Merge branch 'master' into cmake
This commit is contained in:
commit
039f5af34e
|
|
@ -4,11 +4,12 @@
|
|||
/targetroot/
|
||||
/dstroot/
|
||||
|
||||
# Core dumps
|
||||
# Core dumps and traces
|
||||
core
|
||||
core.*
|
||||
vgcore.*
|
||||
*.core
|
||||
*.trace
|
||||
|
||||
# Editor files/directories
|
||||
*.save
|
||||
|
|
@ -26,6 +27,7 @@ vgcore.*
|
|||
/+DESC
|
||||
/+MANIFEST
|
||||
/pkg-plist
|
||||
/debug.ks
|
||||
|
||||
# Produced installer/executables
|
||||
/*.exe
|
||||
|
|
@ -36,6 +38,7 @@ vgcore.*
|
|||
/*.dmg
|
||||
/*.whl
|
||||
/*.txz
|
||||
/*.apk
|
||||
|
||||
# CMake
|
||||
/build/
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ This is a list of all the people who are contributing financially to Panda3D. I
|
|||

|
||||
|
||||
* Sam Edwards
|
||||
* Max Voss
|
||||
|
||||
## Backers
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ Installing Panda3D
|
|||
==================
|
||||
|
||||
The latest Panda3D SDK can be downloaded from
|
||||
[this page](https://www.panda3d.org/download/sdk-1-10-2/).
|
||||
[this page](https://www.panda3d.org/download/sdk-1-10-3/).
|
||||
If you are familiar with installing Python packages, you can use
|
||||
the following comand:
|
||||
|
||||
|
|
@ -64,8 +64,8 @@ depending on whether you are on a 32-bit or 64-bit system, or you can
|
|||
[click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
|
||||
building them from source.
|
||||
|
||||
https://www.panda3d.org/download/panda3d-1.10.2/panda3d-1.10.2-tools-win64.zip
|
||||
https://www.panda3d.org/download/panda3d-1.10.2/panda3d-1.10.2-tools-win32.zip
|
||||
https://www.panda3d.org/download/panda3d-1.10.3/panda3d-1.10.3-tools-win64.zip
|
||||
https://www.panda3d.org/download/panda3d-1.10.3/panda3d-1.10.3-tools-win32.zip
|
||||
|
||||
After acquiring these dependencies, you may simply build Panda3D from the
|
||||
command prompt using the following command. (Change `14.1` to `14` if you are
|
||||
|
|
@ -135,7 +135,7 @@ macOS
|
|||
-----
|
||||
|
||||
On macOS, you will need to download a set of precompiled thirdparty packages in order to
|
||||
compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.2/panda3d-1.10.2-tools-mac.tar.gz).
|
||||
compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.3/panda3d-1.10.3-tools-mac.tar.gz).
|
||||
|
||||
After placing the thirdparty directory inside the panda3d source directory,
|
||||
you may build Panda3D using a command like the following:
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import stat
|
|||
import struct
|
||||
import imp
|
||||
import string
|
||||
import time
|
||||
|
||||
import setuptools
|
||||
import distutils.log
|
||||
|
|
@ -30,6 +31,15 @@ if sys.version_info < (3, 0):
|
|||
# Python 3 defines these subtypes of IOError, but Python 2 doesn't.
|
||||
FileNotFoundError = IOError
|
||||
|
||||
# Warn the user. They might be using Python 2 by accident.
|
||||
print("=================================================================")
|
||||
print("WARNING: You are using Python 2, which will soon be discontinued.")
|
||||
print("WARNING: Please use Python 3 for best results and continued")
|
||||
print("WARNING: support after the EOL date of December 31st, 2019.")
|
||||
print("=================================================================")
|
||||
sys.stdout.flush()
|
||||
time.sleep(4.0)
|
||||
|
||||
|
||||
def _parse_list(input):
|
||||
if isinstance(input, basestring):
|
||||
|
|
@ -229,7 +239,9 @@ class build_apps(setuptools.Command):
|
|||
self.requirements_path = os.path.join(os.getcwd(), 'requirements.txt')
|
||||
self.use_optimized_wheels = True
|
||||
self.optimized_wheel_index = ''
|
||||
self.pypi_extra_indexes = []
|
||||
self.pypi_extra_indexes = [
|
||||
'https://archive.panda3d.org/thirdparty',
|
||||
]
|
||||
self.file_handlers = {}
|
||||
self.exclude_dependencies = [
|
||||
# Windows
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ class FilterManager(DirectObject):
|
|||
|
||||
return winx,winy
|
||||
|
||||
def renderSceneInto(self, depthtex=None, colortex=None, auxtex=None, auxbits=0, textures=None):
|
||||
def renderSceneInto(self, depthtex=None, colortex=None, auxtex=None, auxbits=0, textures=None, fbprops=None):
|
||||
|
||||
""" Causes the scene to be rendered into the supplied textures
|
||||
instead of into the original window. Puts a fullscreen quad
|
||||
|
|
@ -185,7 +185,10 @@ class FilterManager(DirectObject):
|
|||
# Choose the size of the offscreen buffer.
|
||||
|
||||
(winx, winy) = self.getScaledSize(1,1,1)
|
||||
buffer = self.createBuffer("filter-base", winx, winy, texgroup)
|
||||
if fbprops is not None:
|
||||
buffer = self.createBuffer("filter-base", winx, winy, texgroup, fbprops=fbprops)
|
||||
else:
|
||||
buffer = self.createBuffer("filter-base", winx, winy, texgroup)
|
||||
|
||||
if (buffer == None):
|
||||
return None
|
||||
|
|
@ -236,7 +239,7 @@ class FilterManager(DirectObject):
|
|||
|
||||
return quad
|
||||
|
||||
def renderQuadInto(self, name="filter-stage", mul=1, div=1, align=1, depthtex=None, colortex=None, auxtex0=None, auxtex1=None):
|
||||
def renderQuadInto(self, name="filter-stage", mul=1, div=1, align=1, depthtex=None, colortex=None, auxtex0=None, auxtex1=None, fbprops=None):
|
||||
|
||||
""" Creates an offscreen buffer for an intermediate
|
||||
computation. Installs a quad into the buffer. Returns
|
||||
|
|
@ -250,7 +253,10 @@ class FilterManager(DirectObject):
|
|||
|
||||
depthbits = bool(depthtex != None)
|
||||
|
||||
buffer = self.createBuffer(name, winx, winy, texgroup, depthbits)
|
||||
if fbprops is not None:
|
||||
buffer = self.createBuffer(name, winx, winy, texgroup, depthbits, fbprops=fbprops)
|
||||
else:
|
||||
buffer = self.createBuffer(name, winx, winy, texgroup, depthbits)
|
||||
|
||||
if (buffer == None):
|
||||
return None
|
||||
|
|
@ -287,7 +293,7 @@ class FilterManager(DirectObject):
|
|||
|
||||
return quad
|
||||
|
||||
def createBuffer(self, name, xsize, ysize, texgroup, depthbits=1):
|
||||
def createBuffer(self, name, xsize, ysize, texgroup, depthbits=1, fbprops=None):
|
||||
""" Low-level buffer creation. Not intended for public use. """
|
||||
|
||||
winprops = WindowProperties()
|
||||
|
|
@ -297,6 +303,9 @@ class FilterManager(DirectObject):
|
|||
props.setRgbColor(1)
|
||||
props.setDepthBits(depthbits)
|
||||
props.setStereo(self.win.isStereo())
|
||||
if fbprops is not None:
|
||||
props.addProperties(fbprops)
|
||||
|
||||
depthtex, colortex, auxtex0, auxtex1 = texgroup
|
||||
if (auxtex0 != None):
|
||||
props.setAuxRgba(1)
|
||||
|
|
|
|||
|
|
@ -22,10 +22,12 @@ class DirectOptionMenu(DirectButton):
|
|||
# List of items to display on the popup menu
|
||||
('items', [], self.setItems),
|
||||
# Initial item to display on menu button
|
||||
# Can be an interger index or the same string as the button
|
||||
# Can be an integer index or the same string as the button
|
||||
('initialitem', None, DGG.INITOPT),
|
||||
# Amount of padding to place around popup button indicator
|
||||
('popupMarkerBorder', (.1, .1), None),
|
||||
# The initial position of the popup marker
|
||||
('popupMarker_pos', (0, 0, 0), None),
|
||||
# Background color to use to highlight popup menu items
|
||||
('highlightColor', (.5, .5, .5, 1), None),
|
||||
# Extra scale to use on highlight popup menu items
|
||||
|
|
@ -42,6 +44,8 @@ class DirectOptionMenu(DirectButton):
|
|||
DirectButton.__init__(self, parent)
|
||||
# Record any user specified frame size
|
||||
self.initFrameSize = self['frameSize']
|
||||
# Record any user specified popup marker position
|
||||
self.initPopupMarkerPos = self['popupMarker_pos']
|
||||
# Create a small rectangular marker to distinguish this button
|
||||
# as a popup menu button
|
||||
self.popupMarker = self.createcomponent(
|
||||
|
|
@ -168,8 +172,13 @@ class DirectOptionMenu(DirectButton):
|
|||
else:
|
||||
# Or base it upon largest item
|
||||
bounds = [self.minX, self.maxX, self.minZ, self.maxZ]
|
||||
pm.setPos(bounds[1] + pmw/2.0, 0,
|
||||
bounds[2] + (bounds[3] - bounds[2])/2.0)
|
||||
if self.initPopupMarkerPos:
|
||||
# Use specified position
|
||||
pmPos = list(self.initPopupMarkerPos)
|
||||
else:
|
||||
# Or base the position on the frame size.
|
||||
pmPos = [bounds[1] + pmw/2.0, 0, bounds[2] + (bounds[3] - bounds[2])/2.0]
|
||||
pm.setPos(pmPos[0], pmPos[1], pmPos[2])
|
||||
# Adjust popup menu button to fit all items (or use user specified
|
||||
# frame size
|
||||
bounds[1] += pmw
|
||||
|
|
@ -184,6 +193,12 @@ class DirectOptionMenu(DirectButton):
|
|||
Adjust popup position if default position puts it outside of
|
||||
visible screen region
|
||||
"""
|
||||
|
||||
# Needed attributes (such as minZ) won't be set unless the user has specified
|
||||
# items to display. Let's assert that we've given items to work with.
|
||||
items = self['items']
|
||||
assert items and len(items) > 0, 'Cannot show an empty popup menu! You must add items!'
|
||||
|
||||
# Show the menu
|
||||
self.popupMenu.show()
|
||||
# Make sure its at the right scale
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ class SfxPlayer:
|
|||
d = node.getDistance(listenerNode)
|
||||
else:
|
||||
d = node.getDistance(base.cam)
|
||||
if not cutoff:
|
||||
cutoff = self.cutoffDistance
|
||||
if d == None or d > cutoff:
|
||||
volume = 0
|
||||
else:
|
||||
|
|
@ -70,9 +72,6 @@ class SfxPlayer:
|
|||
self, sfx, looping = 0, interrupt = 1, volume = None,
|
||||
time = 0.0, node=None, listenerNode = None, cutoff = None):
|
||||
if sfx:
|
||||
if not cutoff:
|
||||
cutoff = self.cutoffDistance
|
||||
|
||||
self.setFinalVolume(sfx, node, volume, listenerNode, cutoff)
|
||||
|
||||
# don't start over if it's already playing, unless
|
||||
|
|
|
|||
|
|
@ -829,9 +829,11 @@ class ShowBase(DirectObject.DirectObject):
|
|||
win.requestProperties(props)
|
||||
|
||||
mainWindow = False
|
||||
if self.win == None:
|
||||
if self.win is None:
|
||||
mainWindow = True
|
||||
self.win = win
|
||||
if hasattr(self, 'bufferViewer'):
|
||||
self.bufferViewer.win = win
|
||||
|
||||
self.winList.append(win)
|
||||
|
||||
|
|
@ -1677,13 +1679,19 @@ class ShowBase(DirectObject.DirectObject):
|
|||
return self.mouseWatcherNode.getModifierButtons().isDown(
|
||||
KeyboardButton.meta())
|
||||
|
||||
def attachInputDevice(self, device, prefix=None):
|
||||
def attachInputDevice(self, device, prefix=None, watch=False):
|
||||
"""
|
||||
This function attaches an input device to the data graph, which will
|
||||
cause the device to be polled and generate events. If a prefix is
|
||||
given and not None, it is used to prefix events generated by this
|
||||
device, separated by a hyphen.
|
||||
|
||||
The watch argument can be set to True (as of Panda3D 1.10.3) to set up
|
||||
the default MouseWatcher to receive inputs from this device, allowing
|
||||
it to be polled via mouseWatcherNode and control user interfaces.
|
||||
Setting this to True will also make it generate unprefixed events,
|
||||
regardless of the specified prefix.
|
||||
|
||||
If you call this, you should consider calling detachInputDevice when
|
||||
you are done with the device or when it is disconnected.
|
||||
"""
|
||||
|
|
@ -1694,13 +1702,17 @@ class ShowBase(DirectObject.DirectObject):
|
|||
idn = self.dataRoot.attachNewNode(InputDeviceNode(device, device.name))
|
||||
|
||||
# Setup the button thrower to generate events for the device.
|
||||
bt = idn.attachNewNode(ButtonThrower(device.name))
|
||||
if prefix is not None:
|
||||
bt.node().setPrefix(prefix + '-')
|
||||
if prefix is not None or not watch:
|
||||
bt = idn.attachNewNode(ButtonThrower(device.name))
|
||||
if prefix is not None:
|
||||
bt.node().setPrefix(prefix + '-')
|
||||
self.deviceButtonThrowers.append(bt)
|
||||
|
||||
assert self.notify.debug("Attached input device {0} with prefix {1}".format(device, prefix))
|
||||
self.__inputDeviceNodes[device] = idn
|
||||
self.deviceButtonThrowers.append(bt)
|
||||
|
||||
if watch:
|
||||
idn.node().addChild(self.mouseWatcherNode)
|
||||
|
||||
def detachInputDevice(self, device):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -201,17 +201,6 @@ class Lock(core.Mutex):
|
|||
def __init__(self, name = "PythonLock"):
|
||||
core.Mutex.__init__(self, name)
|
||||
|
||||
def acquire(self, blocking = True):
|
||||
if blocking:
|
||||
core.Mutex.acquire(self)
|
||||
return True
|
||||
else:
|
||||
return core.Mutex.tryAcquire(self)
|
||||
|
||||
__enter__ = acquire
|
||||
|
||||
def __exit__(self, t, v, tb):
|
||||
self.release()
|
||||
|
||||
class RLock(core.ReMutex):
|
||||
""" This class provides a wrapper around Panda's ReMutex object.
|
||||
|
|
@ -221,18 +210,6 @@ class RLock(core.ReMutex):
|
|||
def __init__(self, name = "PythonRLock"):
|
||||
core.ReMutex.__init__(self, name)
|
||||
|
||||
def acquire(self, blocking = True):
|
||||
if blocking:
|
||||
core.ReMutex.acquire(self)
|
||||
return True
|
||||
else:
|
||||
return core.ReMutex.tryAcquire(self)
|
||||
|
||||
__enter__ = acquire
|
||||
|
||||
def __exit__(self, t, v, tb):
|
||||
self.release()
|
||||
|
||||
|
||||
class Condition(core.ConditionVarFull):
|
||||
""" This class provides a wrapper around Panda's ConditionVarFull
|
||||
|
|
|
|||
|
|
@ -9,13 +9,16 @@ __all__ = ['AnimPanel', 'ActorControl']
|
|||
# Import Tkinter, Pmw, and the floater code from this directory tree.
|
||||
from direct.tkwidgets.AppShell import *
|
||||
from direct.showbase.TkGlobal import *
|
||||
import Pmw, sys
|
||||
import Pmw, sys, os
|
||||
from direct.task import Task
|
||||
from panda3d.core import Filename, getModelPath
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
from tkinter.simpledialog import askfloat
|
||||
from tkinter.filedialog import askopenfilename
|
||||
else:
|
||||
from tkSimpleDialog import askfloat
|
||||
from tkFileDialog import askopenfilename
|
||||
|
||||
|
||||
FRAMES = 0
|
||||
|
|
@ -273,7 +276,7 @@ class AnimPanel(AppShell):
|
|||
title = 'Load Animation',
|
||||
parent = self.component('hull')
|
||||
)
|
||||
if (animFilename == ''):
|
||||
if not animFilename:
|
||||
# no file selected, canceled
|
||||
return
|
||||
|
||||
|
|
@ -369,8 +372,9 @@ class AnimPanel(AppShell):
|
|||
def destroy(self):
|
||||
# First clean up
|
||||
taskMgr.remove(self.id + '_UpdateTask')
|
||||
self.destroyCallBack()
|
||||
self.destroyCallBack = None
|
||||
if self.destroyCallBack is not None:
|
||||
self.destroyCallBack()
|
||||
self.destroyCallBack = None
|
||||
AppShell.destroy(self)
|
||||
|
||||
class ActorControl(Pmw.MegaWidget):
|
||||
|
|
|
|||
|
|
@ -656,25 +656,35 @@ def rgbPanel(nodePath, callback = None, style = 'mini'):
|
|||
pButton.pack(expand = 1, fill = BOTH)
|
||||
|
||||
# Update menu
|
||||
menu = vgp.component('menubar').component('Valuator Group-menu')
|
||||
menubar = vgp.component('menubar')
|
||||
menubar.deletemenuitems('Valuator Group', 1, 1)
|
||||
|
||||
# Some helper functions
|
||||
# Clear color
|
||||
menu.insert_command(index = 1, label = 'Clear Color',
|
||||
command = lambda: nodePath.clearColor())
|
||||
menubar.addmenuitem(
|
||||
'Valuator Group', 'command',
|
||||
label='Clear Color', command=lambda: nodePath.clearColor())
|
||||
# Set Clear Transparency
|
||||
menu.insert_command(index = 2, label = 'Set Transparency',
|
||||
command = lambda: nodePath.setTransparency(1))
|
||||
menu.insert_command(
|
||||
index = 3, label = 'Clear Transparency',
|
||||
command = lambda: nodePath.clearTransparency())
|
||||
menubar.addmenuitem(
|
||||
'Valuator Group', 'command',
|
||||
label='Set Transparency', command=lambda: nodePath.setTransparency(1))
|
||||
menubar.addmenuitem(
|
||||
'Valuator Group', 'command',
|
||||
label='Clear Transparency', command=lambda: nodePath.clearTransparency())
|
||||
|
||||
|
||||
# System color picker
|
||||
menu.insert_command(index = 4, label = 'Popup Color Picker',
|
||||
command = popupColorPicker)
|
||||
menubar.addmenuitem(
|
||||
'Valuator Group', 'command',
|
||||
label='Popup Color Picker', command=popupColorPicker)
|
||||
|
||||
menu.insert_command(index = 5, label = 'Print to log',
|
||||
command = printToLog)
|
||||
menubar.addmenuitem(
|
||||
'Valuator Group', 'command',
|
||||
label='Print to log', command=printToLog)
|
||||
|
||||
menubar.addmenuitem(
|
||||
'Valuator Group', 'command', 'Dismiss Valuator Group panel',
|
||||
label='Dismiss', command=vgp.destroy)
|
||||
|
||||
def setNodePathColor(color):
|
||||
nodePath.setColor(color[0]/255.0, color[1]/255.0,
|
||||
|
|
@ -724,18 +734,23 @@ def lightRGBPanel(light, style = 'mini'):
|
|||
# Update menu button
|
||||
vgp.component('menubar').component('Valuator Group-button')['text'] = (
|
||||
'Light Control Panel')
|
||||
|
||||
# Add a print button which will also serve as a color tile
|
||||
pButton = Button(vgp.interior(), text = 'Print to Log',
|
||||
bg = getTkColorString(initColor),
|
||||
command = printToLog)
|
||||
pButton.pack(expand = 1, fill = BOTH)
|
||||
|
||||
# Update menu
|
||||
menu = vgp.component('menubar').component('Valuator Group-menu')
|
||||
menubar = vgp.component('menubar')
|
||||
# System color picker
|
||||
menu.insert_command(index = 4, label = 'Popup Color Picker',
|
||||
command = popupColorPicker)
|
||||
menu.insert_command(index = 5, label = 'Print to log',
|
||||
command = printToLog)
|
||||
menubar.addmenuitem(
|
||||
'Valuator Group', 'command',
|
||||
label='Popup Color Picker', command=popupColorPicker)
|
||||
menubar.addmenuitem(
|
||||
'Valuator Group', 'command',
|
||||
label='Print to log', command=printToLog)
|
||||
|
||||
def setLightColor(color):
|
||||
light.setColor(Vec4(color[0]/255.0, color[1]/255.0,
|
||||
color[2]/255.0, color[3]/255.0))
|
||||
|
|
|
|||
|
|
@ -1,3 +1,40 @@
|
|||
------------------------ RELEASE 1.10.3 -----------------------
|
||||
|
||||
This is another bugfix release that addresses a variety of issues
|
||||
in 1.10.2 and further improves the stability.
|
||||
|
||||
* Fix crash when unplugging certain devices on macOS
|
||||
* Fix crash on macOS when using RIME input
|
||||
* Fix logging issues/crashes in apps deployed with Python 2.7
|
||||
* Fix issues when starting in fullscreen on Linux/X11
|
||||
* Fix mapping of several gamepads including Trust GXT 24
|
||||
* Fix Linux crash when no input devices are present
|
||||
* Unbreak support for matrix arrays in vertex data in OpenGL
|
||||
* Allow creating multisample FBO in OpenGL with non-MS host window
|
||||
* Support playing and looping compressed Ogg and WAV audio files
|
||||
* Fix generation of CollisionBox for transformed geometry in .egg
|
||||
* Fix Bullet rigid body transform not updating after reparenting
|
||||
* Fix sporadic color scales with lighting and custom GLSL shader
|
||||
* Prevent faulty shaders from shutting down GSG on some drivers
|
||||
* Allow None as either argument to OdeJoint.attach()
|
||||
* Fix BufferViewer when main window is not opened right away
|
||||
* Properly detect extension of pz/gz compressed video/audio files
|
||||
* Fix for invalid behavior of SparseArray methods to clear bits
|
||||
* FilterManager now allows overriding framebuffer properties
|
||||
* Fix detection of core-only OpenGL profile on some drivers
|
||||
* Add gl-forward-compatible config var for OpenGL context creation
|
||||
* Add paste-emit-keystrokes variable to disable Ctrl+V on Windows
|
||||
* Fix in-place |= operator on Panda types (such as SparseArray)
|
||||
* Fix rare FFmpeg "bad src image pointers" errors after seek
|
||||
* Fix uses of types.InstanceType in some obscure direct functions
|
||||
* Fix capsule-into-sphere collision test in degenerate case
|
||||
* KeyboardButton.ascii_key now also accepts a str character
|
||||
* Fix errors in various Tkinter DIRECT widgets
|
||||
* Expose save_egg_file/save_egg_data functions in Python API
|
||||
* Fix assertion error in BoundingBox.set_min_max
|
||||
* Fix typo in CollisionTraverser.respect_prev_transform property
|
||||
* Properly install Python bindings when building FreeBSD installer
|
||||
|
||||
------------------------ RELEASE 1.10.2 -----------------------
|
||||
|
||||
This release fixes several more bugs, including a few regressions
|
||||
|
|
|
|||
|
|
@ -85,6 +85,44 @@ extern char **GLOBAL_ARGV;
|
|||
extern int GLOBAL_ARGC;
|
||||
#endif
|
||||
|
||||
// One of the responsibilities of ExecutionEnvironment is to determine the path
|
||||
// to the binary file that contains itself (this is useful for making other
|
||||
// components able to read files relative to Panda's installation directory).
|
||||
// When built statically, this is easy - just use the main executable filename.
|
||||
// When built shared, ExecutionEnvironment will introspect the memory map of
|
||||
// the running process to look for dynamic library paths matching this list of
|
||||
// predetermined filenames (ordered most likely to least likely).
|
||||
|
||||
#ifndef LINK_ALL_STATIC
|
||||
static const char *const libp3dtool_filenames[] = {
|
||||
#if defined(LIBP3DTOOL_FILENAMES)
|
||||
|
||||
// The build system is communicating the expected filename(s) for the
|
||||
// libp3dtool dynamic library - no guesswork needed.
|
||||
LIBP3DTOOL_FILENAMES
|
||||
|
||||
#elif defined(WIN32_VC)
|
||||
|
||||
#ifdef _DEBUG
|
||||
"libp3dtool_d.dll",
|
||||
#else
|
||||
"libp3dtool.dll",
|
||||
#endif
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
"libp3dtool." PANDA_ABI_VERSION_STR ".dylib",
|
||||
"libp3dtool.dylib",
|
||||
|
||||
#else
|
||||
|
||||
"libp3dtool.so." PANDA_ABI_VERSION_STR,
|
||||
"libp3dtool.so",
|
||||
|
||||
#endif
|
||||
};
|
||||
#endif /* !LINK_ALL_STATIC */
|
||||
|
||||
// Linux with GNU libc does have global argvargc variables, but we can't
|
||||
// safely access them at stat init time--at least, not in libc5. (It does seem
|
||||
// to work with glibc2, however.)
|
||||
|
|
@ -546,13 +584,14 @@ read_args() {
|
|||
// First, we need to fill in _dtool_name. This contains the full path to
|
||||
// the p3dtool library.
|
||||
|
||||
#ifdef WIN32_VC
|
||||
#ifdef _DEBUG
|
||||
HMODULE dllhandle = GetModuleHandle("libp3dtool_d.dll");
|
||||
#else
|
||||
HMODULE dllhandle = GetModuleHandle("libp3dtool.dll");
|
||||
#endif
|
||||
if (dllhandle != 0) {
|
||||
#ifndef LINK_ALL_STATIC
|
||||
#if defined(WIN32_VC)
|
||||
for (const char *filename : libp3dtool_filenames) {
|
||||
if (!_dtool_name.empty()) break;
|
||||
|
||||
HMODULE dllhandle = GetModuleHandle(filename);
|
||||
if (!dllhandle) continue;
|
||||
|
||||
static const DWORD buffer_size = 1024;
|
||||
wchar_t buffer[buffer_size];
|
||||
DWORD size = GetModuleFileNameW(dllhandle, buffer, buffer_size);
|
||||
|
|
@ -562,46 +601,44 @@ read_args() {
|
|||
_dtool_name = tmp;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#elif defined(__APPLE__)
|
||||
// And on OSX we don't have procselfmaps, but some _dyld_* functions.
|
||||
|
||||
if (_dtool_name.empty()) {
|
||||
uint32_t ic = _dyld_image_count();
|
||||
for (uint32_t i = 0; i < ic; ++i) {
|
||||
const char *buffer = _dyld_get_image_name(i);
|
||||
const char *tail = strrchr(buffer, '/');
|
||||
if (tail && (strcmp(tail, "/libp3dtool." PANDA_ABI_VERSION_STR ".dylib") == 0
|
||||
|| strcmp(tail, "/libp3dtool.dylib") == 0)) {
|
||||
uint32_t ic = _dyld_image_count();
|
||||
for (uint32_t i = 0; i < ic; ++i) {
|
||||
if (!_dtool_name.empty()) break;
|
||||
|
||||
const char *buffer = _dyld_get_image_name(i);
|
||||
if (!buffer) continue;
|
||||
const char *tail = strrchr(buffer, '/');
|
||||
if (!tail) continue;
|
||||
|
||||
for (const char *filename : libp3dtool_filenames) {
|
||||
if (strcmp(&tail[1], filename) == 0) {
|
||||
_dtool_name = buffer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(RTLD_DI_ORIGIN)
|
||||
#elif defined(RTLD_DI_ORIGIN)
|
||||
// When building with glibc/uClibc, we typically have access to RTLD_DI_ORIGIN in Unix-like operating systems.
|
||||
|
||||
char origin[PATH_MAX + 1];
|
||||
|
||||
if (_dtool_name.empty()) {
|
||||
void *dtool_handle = dlopen("libp3dtool.so." PANDA_ABI_VERSION_STR, RTLD_NOW | RTLD_NOLOAD);
|
||||
for (const char *filename : libp3dtool_filenames) {
|
||||
if (!_dtool_name.empty()) break;
|
||||
|
||||
void *dtool_handle = dlopen(filename, RTLD_NOW | RTLD_NOLOAD);
|
||||
if (dtool_handle != nullptr && dlinfo(dtool_handle, RTLD_DI_ORIGIN, origin) != -1) {
|
||||
_dtool_name = origin;
|
||||
_dtool_name += "/libp3dtool.so." PANDA_ABI_VERSION_STR;
|
||||
} else {
|
||||
// Try the version of libp3dtool.so without ABI suffix.
|
||||
dtool_handle = dlopen("libp3dtool.so", RTLD_NOW | RTLD_NOLOAD);
|
||||
if (dtool_handle != nullptr && dlinfo(dtool_handle, RTLD_DI_ORIGIN, origin) != -1) {
|
||||
_dtool_name = origin;
|
||||
_dtool_name += "/libp3dtool.so";
|
||||
}
|
||||
_dtool_name += '/';
|
||||
_dtool_name += filename;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(RTLD_DI_ORIGIN) && defined(RTLD_DI_LINKMAP)
|
||||
#elif defined(RTLD_DI_LINKMAP)
|
||||
// On platforms without RTLD_DI_ORIGIN, we can use dlinfo with RTLD_DI_LINKMAP to get the origin of a loaded library.
|
||||
if (_dtool_name.empty()) {
|
||||
struct link_map *map;
|
||||
|
|
@ -610,16 +647,24 @@ read_args() {
|
|||
#else
|
||||
void *self = dlopen(NULL, RTLD_NOW | RTLD_NOLOAD);
|
||||
#endif
|
||||
dlinfo(self, RTLD_DI_LINKMAP, &map);
|
||||
if (dlinfo(self, RTLD_DI_LINKMAP, &map)) {
|
||||
while (map != nullptr) {
|
||||
if (!_dtool_name.empty()) break;
|
||||
|
||||
while (map != nullptr) {
|
||||
const char *tail = strrchr(map->l_name, '/');
|
||||
const char *head = strchr(map->l_name, '/');
|
||||
if (tail && head && (strcmp(tail, "/libp3dtool.so." PANDA_ABI_VERSION_STR) == 0
|
||||
|| strcmp(tail, "/libp3dtool.so") == 0)) {
|
||||
_dtool_name = head;
|
||||
const char *head = strchr(map->l_name, '/');
|
||||
if (!head) continue;
|
||||
const char *tail = strrchr(head, '/');
|
||||
if (!tail) continue;
|
||||
|
||||
for (const char *filename : libp3dtool_filenames) {
|
||||
if (strcmp(&tail[1], filename) == 0) {
|
||||
_dtool_name = head;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
map = map->l_next;
|
||||
}
|
||||
map = map->l_next;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -634,19 +679,27 @@ read_args() {
|
|||
pifstream maps("/proc/self/maps");
|
||||
#endif
|
||||
while (!maps.fail() && !maps.eof()) {
|
||||
if (!_dtool_name.empty()) break;
|
||||
|
||||
char buffer[PATH_MAX];
|
||||
buffer[0] = 0;
|
||||
maps.getline(buffer, PATH_MAX);
|
||||
const char *tail = strrchr(buffer, '/');
|
||||
const char *head = strchr(buffer, '/');
|
||||
if (tail && head && (strcmp(tail, "/libp3dtool.so." PANDA_ABI_VERSION_STR) == 0
|
||||
|| strcmp(tail, "/libp3dtool.so") == 0)) {
|
||||
_dtool_name = head;
|
||||
if (!head) continue;
|
||||
const char *tail = strrchr(head, '/');
|
||||
if (!tail) continue;
|
||||
|
||||
for (const char *filename : libp3dtool_filenames) {
|
||||
if (strcmp(&tail[1], filename) == 0) {
|
||||
_dtool_name = head;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
maps.close();
|
||||
}
|
||||
#endif
|
||||
#endif /* !LINK_ALL_STATIC */
|
||||
|
||||
// Now, we need to fill in _binary_name. This contains the full path to the
|
||||
// currently running executable.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,317 @@
|
|||
/**
|
||||
* 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 iostream_ext.cxx
|
||||
* @author rdb
|
||||
* @date 2017-07-24
|
||||
*/
|
||||
|
||||
#include "iostream_ext.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
#ifndef CPPPARSER
|
||||
extern struct Dtool_PyTypedObject Dtool_std_istream;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Reads the given number of bytes from the stream, returned as bytes object.
|
||||
* If the given size is -1, all bytes are read from the stream.
|
||||
*/
|
||||
PyObject *Extension<istream>::
|
||||
read(int size) {
|
||||
if (size < 0) {
|
||||
return readall();
|
||||
}
|
||||
|
||||
char *buffer;
|
||||
std::streamsize read_bytes = 0;
|
||||
|
||||
if (size > 0) {
|
||||
std::streambuf *buf = _this->rdbuf();
|
||||
nassertr(buf != nullptr, nullptr);
|
||||
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
PyThreadState *_save;
|
||||
Py_UNBLOCK_THREADS
|
||||
#endif
|
||||
|
||||
buffer = (char *)alloca((size_t)size);
|
||||
read_bytes = buf->sgetn(buffer, (size_t)size);
|
||||
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
Py_BLOCK_THREADS
|
||||
#endif
|
||||
}
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
return PyBytes_FromStringAndSize(buffer, read_bytes);
|
||||
#else
|
||||
return PyString_FromStringAndSize(buffer, read_bytes);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads from the underlying stream, but using at most one call. The number
|
||||
* of returned bytes may therefore be less than what was requested, but it
|
||||
* will always be greater than 0 until EOF is reached.
|
||||
*/
|
||||
PyObject *Extension<istream>::
|
||||
read1(int size) {
|
||||
std::streambuf *buf = _this->rdbuf();
|
||||
nassertr(buf != nullptr, nullptr);
|
||||
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
PyThreadState *_save;
|
||||
Py_UNBLOCK_THREADS
|
||||
#endif
|
||||
|
||||
std::streamsize avail = buf->in_avail();
|
||||
if (avail == 0) {
|
||||
avail = 4096;
|
||||
}
|
||||
|
||||
if (size >= 0 && (std::streamsize)size < avail) {
|
||||
avail = (std::streamsize)size;
|
||||
}
|
||||
|
||||
// Don't read more than 4K at a time
|
||||
if (avail > 4096) {
|
||||
avail = 4096;
|
||||
}
|
||||
|
||||
char *buffer = (char *)alloca(avail);
|
||||
std::streamsize read_bytes = buf->sgetn(buffer, avail);
|
||||
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
Py_BLOCK_THREADS
|
||||
#endif
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
return PyBytes_FromStringAndSize(buffer, read_bytes);
|
||||
#else
|
||||
return PyString_FromStringAndSize(buffer, read_bytes);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all of the bytes in the stream.
|
||||
*/
|
||||
PyObject *Extension<istream>::
|
||||
readall() {
|
||||
std::streambuf *buf = _this->rdbuf();
|
||||
nassertr(buf != nullptr, nullptr);
|
||||
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
PyThreadState *_save;
|
||||
Py_UNBLOCK_THREADS
|
||||
#endif
|
||||
|
||||
std::vector<unsigned char> result;
|
||||
|
||||
static const size_t buffer_size = 4096;
|
||||
char buffer[buffer_size];
|
||||
|
||||
std::streamsize count = buf->sgetn(buffer, buffer_size);
|
||||
while (count != 0) {
|
||||
thread_consider_yield();
|
||||
result.insert(result.end(), buffer, buffer + count);
|
||||
count = buf->sgetn(buffer, buffer_size);
|
||||
}
|
||||
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
Py_BLOCK_THREADS
|
||||
#endif
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
return PyBytes_FromStringAndSize((char *)result.data(), result.size());
|
||||
#else
|
||||
return PyString_FromStringAndSize((char *)result.data(), result.size());
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads bytes into a preallocated, writable, bytes-like object, returning the
|
||||
* number of bytes read.
|
||||
*/
|
||||
std::streamsize Extension<istream>::
|
||||
readinto(PyObject *b) {
|
||||
std::streambuf *buf = _this->rdbuf();
|
||||
nassertr(buf != nullptr, 0);
|
||||
|
||||
Py_buffer view;
|
||||
if (PyObject_GetBuffer(b, &view, PyBUF_CONTIG) == -1) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"write() requires a contiguous, read-write bytes-like object");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
PyThreadState *_save;
|
||||
Py_UNBLOCK_THREADS
|
||||
#endif
|
||||
|
||||
std::streamsize count = buf->sgetn((char *)view.buf, view.len);
|
||||
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
Py_BLOCK_THREADS
|
||||
#endif
|
||||
|
||||
PyBuffer_Release(&view);
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts one line up to and including the trailing newline character.
|
||||
* Returns empty string when the end of file is reached.
|
||||
*/
|
||||
PyObject *Extension<istream>::
|
||||
readline(int size) {
|
||||
std::streambuf *buf = _this->rdbuf();
|
||||
nassertr(buf != nullptr, nullptr);
|
||||
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
PyThreadState *_save;
|
||||
Py_UNBLOCK_THREADS
|
||||
#endif
|
||||
|
||||
std::string line;
|
||||
int ch = buf->sbumpc();
|
||||
while (ch != EOF && (--size) != 0) {
|
||||
line.push_back(ch);
|
||||
if (ch == '\n') {
|
||||
// Here's the newline character.
|
||||
break;
|
||||
}
|
||||
ch = buf->sbumpc();
|
||||
}
|
||||
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
Py_BLOCK_THREADS
|
||||
#endif
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
return PyBytes_FromStringAndSize(line.data(), line.size());
|
||||
#else
|
||||
return PyString_FromStringAndSize(line.data(), line.size());
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all the lines at once and returns a list. Also see the documentation
|
||||
* for readline().
|
||||
*/
|
||||
PyObject *Extension<istream>::
|
||||
readlines(int hint) {
|
||||
PyObject *lst = PyList_New(0);
|
||||
if (lst == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PyObject *py_line = readline(-1);
|
||||
|
||||
if (hint < 0) {
|
||||
while (Py_SIZE(py_line) > 0) {
|
||||
PyList_Append(lst, py_line);
|
||||
Py_DECREF(py_line);
|
||||
|
||||
py_line = readline(-1);
|
||||
}
|
||||
} else {
|
||||
size_t totchars = 0;
|
||||
while (Py_SIZE(py_line) > 0) {
|
||||
totchars += Py_SIZE(py_line);
|
||||
PyList_Append(lst, py_line);
|
||||
Py_DECREF(py_line);
|
||||
|
||||
if (totchars > hint) {
|
||||
break;
|
||||
}
|
||||
|
||||
py_line = readline(-1);
|
||||
}
|
||||
}
|
||||
|
||||
return lst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Yields continuously to read all the lines from the istream.
|
||||
*/
|
||||
static PyObject *gen_next(PyObject *self) {
|
||||
istream *stream = nullptr;
|
||||
if (!Dtool_Call_ExtractThisPointer(self, Dtool_std_istream, (void **)&stream)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PyObject *line = invoke_extension(stream).readline();
|
||||
if (Py_SIZE(line) > 0) {
|
||||
return line;
|
||||
} else {
|
||||
PyErr_SetObject(PyExc_StopIteration, nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over the lines of the file.
|
||||
*/
|
||||
PyObject *Extension<istream>::
|
||||
__iter__(PyObject *self) {
|
||||
return Dtool_NewGenerator(self, &gen_next);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the bytes object to the stream.
|
||||
*/
|
||||
void Extension<ostream>::
|
||||
write(PyObject *b) {
|
||||
std::streambuf *buf = _this->rdbuf();
|
||||
nassertv(buf != nullptr);
|
||||
|
||||
Py_buffer view;
|
||||
if (PyObject_GetBuffer(b, &view, PyBUF_CONTIG_RO) == -1) {
|
||||
PyErr_SetString(PyExc_TypeError, "write() requires a contiguous buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
PyThreadState *_save;
|
||||
Py_UNBLOCK_THREADS
|
||||
buf->sputn((const char *)view.buf, view.len);
|
||||
Py_BLOCK_THREADS
|
||||
#else
|
||||
buf->sputn((const char *)view.buf, view.len);
|
||||
#endif
|
||||
|
||||
PyBuffer_Release(&view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a list of lines to the stream. Line separators are not added, so it
|
||||
* is usual for each of the lines provided to have a line separator at the
|
||||
* end.
|
||||
*/
|
||||
void Extension<ostream>::
|
||||
writelines(PyObject *lines) {
|
||||
PyObject *seq = PySequence_Fast(lines, "writelines() expects a sequence");
|
||||
if (seq == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
PyObject **items = PySequence_Fast_ITEMS(seq);
|
||||
Py_ssize_t len = PySequence_Fast_GET_SIZE(seq);
|
||||
|
||||
for (Py_ssize_t i = 0; i < len; ++i) {
|
||||
write(items[i]);
|
||||
}
|
||||
|
||||
Py_DECREF(seq);
|
||||
}
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* 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 iostream_ext.h
|
||||
* @author rdb
|
||||
* @date 2017-07-24
|
||||
*/
|
||||
|
||||
#ifndef IOSTREAM_EXT_H
|
||||
#define IOSTREAM_EXT_H
|
||||
|
||||
#include "dtoolbase.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
#include "extension.h"
|
||||
#include <iostream>
|
||||
#include "py_panda.h"
|
||||
|
||||
/**
|
||||
* These classes define the extension methods for istream and ostream, which
|
||||
* are called instead of any C++ methods with the same prototype.
|
||||
*
|
||||
* These are designed to allow streams to be treated as file-like objects.
|
||||
*/
|
||||
template<>
|
||||
class Extension<istream> : public ExtensionBase<istream> {
|
||||
public:
|
||||
PyObject *read(int size=-1);
|
||||
PyObject *read1(int size=-1);
|
||||
PyObject *readall();
|
||||
std::streamsize readinto(PyObject *b);
|
||||
|
||||
PyObject *readline(int size=-1);
|
||||
PyObject *readlines(int hint=-1);
|
||||
PyObject *__iter__(PyObject *self);
|
||||
};
|
||||
|
||||
template<>
|
||||
class Extension<ostream> : public ExtensionBase<ostream> {
|
||||
public:
|
||||
void write(PyObject *b);
|
||||
void writelines(PyObject *lines);
|
||||
};
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#endif // IOSTREAM_EXT_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#include "filename_ext.cxx"
|
||||
#include "globPattern_ext.cxx"
|
||||
#include "iostream_ext.cxx"
|
||||
#include "textEncoder_ext.cxx"
|
||||
|
|
|
|||
|
|
@ -431,6 +431,12 @@ get_slotted_function_def(Object *obj, Function *func, FunctionRemap *remap,
|
|||
return true;
|
||||
}
|
||||
|
||||
if (method_name == "operator |=") {
|
||||
def._answer_location = "nb_inplace_or";
|
||||
def._wrapper_type = WT_inplace_binary_operator;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (method_name == "__ipow__") {
|
||||
def._answer_location = "nb_inplace_power";
|
||||
def._wrapper_type = WT_inplace_ternary_operator;
|
||||
|
|
@ -4946,13 +4952,14 @@ write_function_instance(ostream &out, FunctionRemap *remap,
|
|||
expected_params += "NoneType";
|
||||
|
||||
} else if (TypeManager::is_char(type)) {
|
||||
indent(out, indent_level) << "char " << param_name << default_expr << ";\n";
|
||||
indent(out, indent_level) << "char *" << param_name << "_str;\n";
|
||||
indent(out, indent_level) << "Py_ssize_t " << param_name << "_len;\n";
|
||||
|
||||
format_specifiers += "c";
|
||||
parameter_list += ", &" + param_name;
|
||||
format_specifiers += "s#";
|
||||
parameter_list += ", &" + param_name + "_str, &" + param_name + "_len";
|
||||
extra_param_check << " && " << param_name << "_len == 1";
|
||||
|
||||
// extra_param_check << " && isascii(" << param_name << ")";
|
||||
pexpr_string = "(char) " + param_name;
|
||||
pexpr_string = param_name + "_str[0]";
|
||||
expected_params += "char";
|
||||
only_pyobjects = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
// Filename: iostream
|
||||
// Created by: drose (12May00)
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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."
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* 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 iostream
|
||||
* @author drose
|
||||
* @date 2000-05-12
|
||||
*/
|
||||
|
||||
// This file, and all the other files in this directory, aren't
|
||||
// intended to be compiled--they're just parsed by CPPParser (and
|
||||
|
|
@ -34,6 +33,9 @@ namespace std {
|
|||
__published:
|
||||
ostream(const ostream&) = delete;
|
||||
|
||||
__extension void write(PyObject *b);
|
||||
__extension void writelines(PyObject *lines);
|
||||
|
||||
void put(char c);
|
||||
void flush();
|
||||
streampos tellp();
|
||||
|
|
@ -43,10 +45,20 @@ namespace std {
|
|||
protected:
|
||||
ostream(ostream &&);
|
||||
};
|
||||
|
||||
class istream : virtual public ios {
|
||||
__published:
|
||||
istream(const istream&) = delete;
|
||||
|
||||
__extension PyObject *read(int size=-1);
|
||||
__extension PyObject *read1(int size=-1);
|
||||
__extension PyObject *readall();
|
||||
__extension std::streamsize readinto(PyObject *b);
|
||||
|
||||
__extension PyObject *readline(int size=-1);
|
||||
__extension PyObject *readlines(int hint=-1);
|
||||
__extension PyObject *__iter__(PyObject *self);
|
||||
|
||||
int get();
|
||||
streampos tellg();
|
||||
void seekg(streampos pos);
|
||||
|
|
@ -55,6 +67,7 @@ namespace std {
|
|||
protected:
|
||||
istream(istream &&);
|
||||
};
|
||||
|
||||
class iostream : public istream, public ostream {
|
||||
__published:
|
||||
iostream(const iostream&) = delete;
|
||||
|
|
|
|||
|
|
@ -12,3 +12,23 @@
|
|||
*/
|
||||
|
||||
#include "encryptStream.h"
|
||||
|
||||
/**
|
||||
* Must be called immediately after open_read(). Decrypts the given number of
|
||||
* bytes and checks that they match. The amount of header bytes are added to
|
||||
* an offset so that skipping to 0 will skip past the header.
|
||||
*
|
||||
* Returns true if the read magic matches the given magic, false on error.
|
||||
*/
|
||||
bool IDecryptStream::
|
||||
read_magic(const char *magic, size_t size) {
|
||||
char *this_magic = (char *)alloca(size);
|
||||
read(this_magic, size);
|
||||
|
||||
if (!fail() && gcount() == size && memcmp(this_magic, magic, size) == 0) {
|
||||
_buf.set_magic_length(size);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@ PUBLISHED:
|
|||
MAKE_PROPERTY(key_length, get_key_length);
|
||||
MAKE_PROPERTY(iteration_count, get_iteration_count);
|
||||
|
||||
public:
|
||||
bool read_magic(const char *magic, size_t size);
|
||||
|
||||
private:
|
||||
EncryptStreamBuf _buf;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -81,3 +81,21 @@ INLINE int EncryptStreamBuf::
|
|||
get_iteration_count() const {
|
||||
return _iteration_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the amount of the encrypted data at the beginning that are skipped
|
||||
* when seeking back to zero.
|
||||
*/
|
||||
INLINE void EncryptStreamBuf::
|
||||
set_magic_length(size_t length) {
|
||||
_magic_length = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the amount of the encrypted data at the beginning that are skipped
|
||||
* when seeking back to zero.
|
||||
*/
|
||||
INLINE size_t EncryptStreamBuf::
|
||||
get_magic_length() const {
|
||||
return _magic_length;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@ open_read(std::istream *source, bool owns_source, const std::string &password) {
|
|||
|
||||
_read_overflow_buffer = new unsigned char[_read_block_size];
|
||||
_in_read_overflow_buffer = 0;
|
||||
_finished = false;
|
||||
thread_consider_yield();
|
||||
}
|
||||
|
||||
|
|
@ -322,6 +323,57 @@ close_write() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements seeking within the stream. EncryptStreamBuf only allows seeking
|
||||
* back to the beginning of the stream.
|
||||
*/
|
||||
std::streampos EncryptStreamBuf::
|
||||
seekoff(std::streamoff off, ios_seekdir dir, ios_openmode which) {
|
||||
if (which != std::ios::in) {
|
||||
// We can only do this with the input stream.
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (off != 0 || dir != std::ios::beg) {
|
||||
// We only know how to reposition to the beginning.
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t n = egptr() - gptr();
|
||||
gbump(n);
|
||||
|
||||
if (_source->rdbuf()->pubseekpos(0, std::ios::in) == (std::streampos)0) {
|
||||
int result = EVP_DecryptInit(_read_ctx, nullptr, nullptr, nullptr);
|
||||
nassertr_always(result > 0, -1);
|
||||
|
||||
_source->clear();
|
||||
_in_read_overflow_buffer = 0;
|
||||
_finished = false;
|
||||
|
||||
// Skip past the header.
|
||||
int iv_length = EVP_CIPHER_CTX_iv_length(_read_ctx);
|
||||
_source->ignore(6 + iv_length);
|
||||
|
||||
// Ignore the magic bytes.
|
||||
size_t magic_length = get_magic_length();
|
||||
char *buffer = (char *)alloca(magic_length);
|
||||
if (read_chars(buffer, magic_length) == magic_length) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements seeking within the stream. EncryptStreamBuf only allows seeking
|
||||
* back to the beginning of the stream.
|
||||
*/
|
||||
std::streampos EncryptStreamBuf::
|
||||
seekpos(std::streampos pos, ios_openmode which) {
|
||||
return seekoff(pos, std::ios::beg, which);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the system ostream implementation when its internal buffer is
|
||||
* filled, plus one character.
|
||||
|
|
@ -423,7 +475,7 @@ read_chars(char *start, size_t length) {
|
|||
|
||||
do {
|
||||
// Get more bytes from the stream.
|
||||
if (_read_ctx == nullptr) {
|
||||
if (_read_ctx == nullptr || _finished) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -439,8 +491,7 @@ read_chars(char *start, size_t length) {
|
|||
} else {
|
||||
result =
|
||||
EVP_DecryptFinal(_read_ctx, read_buffer, &bytes_read);
|
||||
EVP_CIPHER_CTX_free(_read_ctx);
|
||||
_read_ctx = nullptr;
|
||||
_finished = true;
|
||||
}
|
||||
|
||||
if (result <= 0) {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,12 @@ public:
|
|||
INLINE void set_iteration_count(int iteration_count);
|
||||
INLINE int get_iteration_count() const;
|
||||
|
||||
INLINE void set_magic_length(size_t length);
|
||||
INLINE size_t get_magic_length() const;
|
||||
|
||||
virtual std::streampos seekoff(std::streamoff off, ios_seekdir dir, ios_openmode which);
|
||||
virtual std::streampos seekpos(std::streampos pos, ios_openmode which);
|
||||
|
||||
protected:
|
||||
virtual int overflow(int c);
|
||||
virtual int sync();
|
||||
|
|
@ -71,6 +77,9 @@ private:
|
|||
|
||||
EVP_CIPHER_CTX *_write_ctx;
|
||||
size_t _write_block_size;
|
||||
|
||||
size_t _magic_length = 0;
|
||||
bool _finished = false;
|
||||
};
|
||||
|
||||
#include "encryptStreamBuf.I"
|
||||
|
|
|
|||
|
|
@ -43,7 +43,18 @@ StreamReader(const StreamReader ©) :
|
|||
}
|
||||
|
||||
/**
|
||||
* The copy constructor does not copy ownership of the stream.
|
||||
* The move constructor steals ownership of the stream.
|
||||
*/
|
||||
INLINE StreamReader::
|
||||
StreamReader(StreamReader &&from) noexcept :
|
||||
_in(from._in),
|
||||
_owns_stream(from._owns_stream)
|
||||
{
|
||||
from._owns_stream = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The copy assignment operator does not copy ownership of the stream.
|
||||
*/
|
||||
INLINE void StreamReader::
|
||||
operator = (const StreamReader ©) {
|
||||
|
|
@ -54,6 +65,19 @@ operator = (const StreamReader ©) {
|
|||
_owns_stream = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The move assignment operator steals ownership of the stream.
|
||||
*/
|
||||
INLINE void StreamReader::
|
||||
operator = (StreamReader &&from) noexcept {
|
||||
if (_owns_stream) {
|
||||
delete _in;
|
||||
}
|
||||
_in = from._in;
|
||||
_owns_stream = from._owns_stream;
|
||||
from._owns_stream = false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ public:
|
|||
PUBLISHED:
|
||||
INLINE explicit StreamReader(std::istream *in, bool owns_stream);
|
||||
INLINE StreamReader(const StreamReader ©);
|
||||
INLINE StreamReader(StreamReader &&from) noexcept;
|
||||
INLINE void operator = (const StreamReader ©);
|
||||
INLINE void operator = (StreamReader &&from) noexcept;
|
||||
INLINE ~StreamReader();
|
||||
|
||||
INLINE std::istream *get_istream() const;
|
||||
|
|
|
|||
|
|
@ -51,7 +51,21 @@ StreamWriter(const StreamWriter ©) :
|
|||
}
|
||||
|
||||
/**
|
||||
* The copy constructor does not copy ownership of the stream.
|
||||
* The move constructor steals ownership of the stream.
|
||||
*/
|
||||
INLINE StreamWriter::
|
||||
StreamWriter(StreamWriter &&from) noexcept :
|
||||
#ifdef HAVE_PYTHON
|
||||
softspace(0),
|
||||
#endif
|
||||
_out(from._out),
|
||||
_owns_stream(from._owns_stream)
|
||||
{
|
||||
from._owns_stream = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The copy assignment operator does not copy ownership of the stream.
|
||||
*/
|
||||
INLINE void StreamWriter::
|
||||
operator = (const StreamWriter ©) {
|
||||
|
|
@ -62,6 +76,19 @@ operator = (const StreamWriter ©) {
|
|||
_owns_stream = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The move assignment operator steals ownership of the stream.
|
||||
*/
|
||||
INLINE void StreamWriter::
|
||||
operator = (StreamWriter &&from) noexcept {
|
||||
if (_owns_stream) {
|
||||
delete _out;
|
||||
}
|
||||
_out = from._out;
|
||||
_owns_stream = from._owns_stream;
|
||||
from._owns_stream = false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@ public:
|
|||
PUBLISHED:
|
||||
INLINE explicit StreamWriter(std::ostream *out, bool owns_stream);
|
||||
INLINE StreamWriter(const StreamWriter ©);
|
||||
INLINE StreamWriter(StreamWriter &&from) noexcept;
|
||||
INLINE void operator = (const StreamWriter ©);
|
||||
INLINE void operator = (StreamWriter &&from) noexcept;
|
||||
INLINE ~StreamWriter();
|
||||
|
||||
INLINE std::ostream *get_ostream() const;
|
||||
|
|
|
|||
|
|
@ -1009,12 +1009,26 @@ if (COMPILER=="GCC"):
|
|||
|
||||
if GetTarget() == 'darwin':
|
||||
LibName("ALWAYS", "-framework AppKit")
|
||||
LibName("IOKIT", "-framework IOKit")
|
||||
LibName("QUARTZ", "-framework Quartz")
|
||||
LibName("AGL", "-framework AGL")
|
||||
LibName("CARBON", "-framework Carbon")
|
||||
LibName("COCOA", "-framework Cocoa")
|
||||
# Fix for a bug in OSX Leopard:
|
||||
LibName("GL", "-dylib_file /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib:/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib")
|
||||
|
||||
# Temporary exceptions to removal of this flag
|
||||
if not PkgSkip("ROCKET"):
|
||||
LibName("ROCKET", "-undefined dynamic_lookup")
|
||||
if not PkgSkip("FFMPEG"):
|
||||
LibName("FFMPEG", "-undefined dynamic_lookup")
|
||||
if not PkgSkip("ASSIMP"):
|
||||
LibName("ASSIMP", "-undefined dynamic_lookup")
|
||||
if not PkgSkip("OPENEXR"):
|
||||
LibName("OPENEXR", "-undefined dynamic_lookup")
|
||||
if not PkgSkip("VRPN"):
|
||||
LibName("VRPN", "-undefined dynamic_lookup")
|
||||
|
||||
if GetTarget() == 'android':
|
||||
LibName("ALWAYS", '-llog')
|
||||
LibName("ANDROID", '-landroid')
|
||||
|
|
@ -1140,6 +1154,7 @@ def BracketNameWithQuotes(name):
|
|||
# Workaround for OSX bug - compiler doesn't like those flags quoted.
|
||||
if (name.startswith("-framework")): return name
|
||||
if (name.startswith("-dylib_file")): return name
|
||||
if (name.startswith("-undefined ")): return name
|
||||
|
||||
# Don't add quotes when it's not necessary.
|
||||
if " " not in name: return name
|
||||
|
|
@ -1813,9 +1828,11 @@ def CompileLink(dll, obj, opts):
|
|||
cmd += ' -Wl,--allow-shlib-undefined'
|
||||
else:
|
||||
if (GetTarget() == "darwin"):
|
||||
cmd = cxx + ' -undefined dynamic_lookup'
|
||||
if ("BUNDLE" in opts or GetOrigExt(dll) == ".pyd"):
|
||||
cmd += ' -bundle '
|
||||
cmd = cxx
|
||||
if GetOrigExt(dll) == ".pyd":
|
||||
cmd += ' -bundle -undefined dynamic_lookup'
|
||||
elif "BUNDLE" in opts:
|
||||
cmd += ' -bundle'
|
||||
else:
|
||||
install_name = '@loader_path/../lib/' + os.path.basename(dll)
|
||||
cmd += ' -dynamiclib -install_name ' + install_name
|
||||
|
|
@ -2325,7 +2342,6 @@ DTOOL_CONFIG=[
|
|||
("COMPILE_IN_DEFAULT_FONT", '1', '1'),
|
||||
("STDFLOAT_DOUBLE", 'UNDEF', 'UNDEF'),
|
||||
("HAVE_MAYA", '1', '1'),
|
||||
("HAVE_SOFTIMAGE", 'UNDEF', 'UNDEF'),
|
||||
("REPORT_OPENSSL_ERRORS", '1', '1'),
|
||||
("USE_PANDAFILESTREAM", '1', '1'),
|
||||
("USE_DELETED_CHAIN", '1', '1'),
|
||||
|
|
@ -2866,6 +2882,14 @@ for basename in del_files:
|
|||
p3d_init = """"Python bindings for the Panda3D libraries"
|
||||
|
||||
__version__ = '%s'
|
||||
|
||||
if __debug__:
|
||||
import sys
|
||||
if sys.version_info < (3, 0):
|
||||
sys.stderr.write("WARNING: Python 2.7 will reach EOL after December 31, 2019.\\n")
|
||||
sys.stderr.write("To suppress this warning, upgrade to Python 3.\\n")
|
||||
sys.stderr.flush()
|
||||
del sys
|
||||
""" % (WHLVERSION)
|
||||
|
||||
if GetTarget() == 'windows':
|
||||
|
|
@ -3661,6 +3685,7 @@ IGATEFILES += [
|
|||
"globPattern_ext.h",
|
||||
"pandaFileStream.h",
|
||||
"lineStream.h",
|
||||
"iostream_ext.h",
|
||||
]
|
||||
TargetAdd('libp3dtoolutil.in', opts=OPTS, input=IGATEFILES)
|
||||
TargetAdd('libp3dtoolutil.in', opts=['IMOD:panda3d.core', 'ILIB:libp3dtoolutil', 'SRCDIR:dtool/src/dtoolutil'])
|
||||
|
|
@ -4204,7 +4229,7 @@ if (not RUNTIME):
|
|||
OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', 'HARFBUZZ',
|
||||
'TIFF', 'OPENEXR', 'ZLIB', 'OPENSSL', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2',
|
||||
'SQUISH', 'NVIDIACG', 'VORBIS', 'OPUS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI',
|
||||
'SETUPAPI']
|
||||
'SETUPAPI', 'IOKIT']
|
||||
|
||||
TargetAdd('panda_panda.obj', opts=OPTS, input='panda.cxx')
|
||||
|
||||
|
|
@ -4843,7 +4868,7 @@ if (GetTarget() == 'darwin' and PkgSkip("COCOA")==0 and PkgSkip("GL")==0 and not
|
|||
if (PkgSkip('PANDAFX')==0):
|
||||
TargetAdd('libpandagl.dll', input='libpandafx.dll')
|
||||
TargetAdd('libpandagl.dll', input=COMMON_PANDA_LIBS)
|
||||
TargetAdd('libpandagl.dll', opts=['MODULE', 'GL', 'NVIDIACG', 'CGGL', 'COCOA', 'CARBON'])
|
||||
TargetAdd('libpandagl.dll', opts=['MODULE', 'GL', 'NVIDIACG', 'CGGL', 'COCOA', 'CARBON', 'QUARTZ'])
|
||||
|
||||
#
|
||||
# DIRECTORY: panda/src/wgldisplay/
|
||||
|
|
|
|||
|
|
@ -596,10 +596,23 @@ def makewheel(version, output_dir, platform=None):
|
|||
|
||||
# Write the panda3d tree. We use a custom empty __init__ since the
|
||||
# default one adds the bin directory to the PATH, which we don't have.
|
||||
whl.write_file_data('panda3d/__init__.py', """"Python bindings for the Panda3D libraries"
|
||||
p3d_init = """"Python bindings for the Panda3D libraries"
|
||||
|
||||
__version__ = '{0}'
|
||||
""".format(version))
|
||||
""".format(version)
|
||||
|
||||
if '27' in ABI_TAG:
|
||||
p3d_init += """
|
||||
if __debug__:
|
||||
import sys
|
||||
if sys.version_info < (3, 0):
|
||||
sys.stderr.write("WARNING: Python 2.7 will reach EOL after December 31, 2019.\\n")
|
||||
sys.stderr.write("To suppress this warning, upgrade to Python 3.\\n")
|
||||
sys.stderr.flush()
|
||||
del sys
|
||||
"""
|
||||
|
||||
whl.write_file_data('panda3d/__init__.py', p3d_init)
|
||||
|
||||
# Copy the extension modules from the panda3d directory.
|
||||
ext_suffix = GetExtensionSuffix()
|
||||
|
|
|
|||
|
|
@ -17,31 +17,34 @@ from optparse import OptionParser
|
|||
def test_wheel(wheel, verbose=False):
|
||||
envdir = tempfile.mkdtemp(prefix="venv-")
|
||||
print("Setting up virtual environment in {0}".format(envdir))
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
subprocess.call([sys.executable, "-m", "venv", "--clear", envdir])
|
||||
else:
|
||||
subprocess.call([sys.executable, "-m", "virtualenv", "--clear", envdir])
|
||||
sys.stdout.flush()
|
||||
|
||||
# Make sure pip is up-to-date first.
|
||||
if subprocess.call([sys.executable, "-m", "pip", "install", "-U", "pip"]) != 0:
|
||||
shutil.rmtree(envdir)
|
||||
sys.exit(1)
|
||||
subprocess.call([sys.executable, "-B", "-m", "pip", "install", "-U", "pip"])
|
||||
|
||||
# Install pytest into the environment, as well as our wheel.
|
||||
if sys.platform == "win32":
|
||||
pip = os.path.join(envdir, "Scripts", "pip.exe")
|
||||
# Create a virtualenv.
|
||||
if sys.version_info >= (3, 0):
|
||||
subprocess.call([sys.executable, "-B", "-m", "venv", "--clear", envdir])
|
||||
else:
|
||||
pip = os.path.join(envdir, "bin", "pip")
|
||||
if subprocess.call([pip, "install", "pytest", wheel]) != 0:
|
||||
shutil.rmtree(envdir)
|
||||
sys.exit(1)
|
||||
subprocess.call([sys.executable, "-B", "-m", "virtualenv", "--clear", envdir])
|
||||
|
||||
# Run the test suite.
|
||||
# Determine the path to the Python interpreter.
|
||||
if sys.platform == "win32":
|
||||
python = os.path.join(envdir, "Scripts", "python.exe")
|
||||
else:
|
||||
python = os.path.join(envdir, "bin", "python")
|
||||
|
||||
# Upgrade pip inside the environment too.
|
||||
if subprocess.call([python, "-m", "pip", "install", "-U", "pip"]) != 0:
|
||||
shutil.rmtree(envdir)
|
||||
sys.exit(1)
|
||||
|
||||
# Install pytest into the environment, as well as our wheel.
|
||||
if subprocess.call([python, "-m", "pip", "install", "pytest", wheel]) != 0:
|
||||
shutil.rmtree(envdir)
|
||||
sys.exit(1)
|
||||
|
||||
# Run the test suite.
|
||||
test_cmd = [python, "-m", "pytest", "tests"]
|
||||
if verbose:
|
||||
test_cmd.append("--verbose")
|
||||
|
|
|
|||
|
|
@ -442,7 +442,7 @@ get_sound_data(MovieAudio *movie, int mode) {
|
|||
int channels = stream->audio_channels();
|
||||
int samples = (int)(stream->length() * stream->audio_rate());
|
||||
int16_t *data = new int16_t[samples * channels];
|
||||
stream->read_samples(samples, data);
|
||||
samples = stream->read_samples(samples, data);
|
||||
alBufferData(sd->_sample,
|
||||
(channels>1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16,
|
||||
data, samples * channels * 2, stream->audio_rate());
|
||||
|
|
|
|||
|
|
@ -373,7 +373,6 @@ read_stream_data(int bytelen, unsigned char *buffer) {
|
|||
nassertr(has_sound_data(), 0);
|
||||
|
||||
MovieAudioCursor *cursor = _sd->_stream;
|
||||
double length = cursor->length();
|
||||
int channels = cursor->audio_channels();
|
||||
int rate = cursor->audio_rate();
|
||||
int space = bytelen / (channels * 2);
|
||||
|
|
@ -381,7 +380,7 @@ read_stream_data(int bytelen, unsigned char *buffer) {
|
|||
|
||||
while (space && (_loops_completed < _playing_loops)) {
|
||||
double t = cursor->tell();
|
||||
double remain = length - t;
|
||||
double remain = cursor->length() - t;
|
||||
if (remain > 60.0) {
|
||||
remain = 60.0;
|
||||
}
|
||||
|
|
@ -403,9 +402,20 @@ read_stream_data(int bytelen, unsigned char *buffer) {
|
|||
if (samples > _sd->_stream->ready()) {
|
||||
samples = _sd->_stream->ready();
|
||||
}
|
||||
cursor->read_samples(samples, (int16_t *)buffer);
|
||||
size_t hval = AddHash::add_hash(0, (uint8_t*)buffer, samples*channels*2);
|
||||
audio_debug("Streaming " << cursor->get_source()->get_name() << " at " << t << " hash " << hval);
|
||||
samples = cursor->read_samples(samples, (int16_t *)buffer);
|
||||
if (audio_cat.is_debug()) {
|
||||
size_t hval = AddHash::add_hash(0, (uint8_t*)buffer, samples*channels*2);
|
||||
audio_debug("Streaming " << cursor->get_source()->get_name() << " at " << t << " hash " << hval);
|
||||
}
|
||||
if (samples == 0) {
|
||||
_loops_completed += 1;
|
||||
cursor->seek(0.0);
|
||||
if (_playing_loops >= 1000000000) {
|
||||
// Prevent infinite loop if endlessly looping empty sound
|
||||
return fill;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
fill += samples;
|
||||
space -= samples;
|
||||
buffer += (samples * channels * 2);
|
||||
|
|
|
|||
|
|
@ -359,6 +359,20 @@ do_transform_changed() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void BulletRigidBodyNode::
|
||||
parents_changed() {
|
||||
|
||||
if (_motion.sync_disabled()) return;
|
||||
|
||||
if (get_num_parents() > 0) {
|
||||
LightMutexHolder holder(BulletWorld::get_global_lock());
|
||||
do_transform_changed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ public:
|
|||
void do_sync_b2p();
|
||||
|
||||
protected:
|
||||
virtual void parents_changed();
|
||||
virtual void transform_changed();
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
* This is a light wrapper around GLGraphicsBuffer (ie. FBOs) to interface
|
||||
* with Cocoa contexts, so that it can be used without a host window.
|
||||
*/
|
||||
class CocoaGraphicsBuffer : public GLGraphicsBuffer {
|
||||
class EXPCL_PANDA_COCOADISPLAY CocoaGraphicsBuffer : public GLGraphicsBuffer {
|
||||
public:
|
||||
CocoaGraphicsBuffer(GraphicsEngine *engine, GraphicsPipe *pipe,
|
||||
const std::string &name,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class FrameBufferProperties;
|
|||
* This graphics pipe represents the interface for creating OpenGL graphics
|
||||
* windows on a Cocoa-based (e.g. Mac OS X) client.
|
||||
*/
|
||||
class CocoaGraphicsPipe : public GraphicsPipe {
|
||||
class EXPCL_PANDA_COCOADISPLAY CocoaGraphicsPipe : public GraphicsPipe {
|
||||
public:
|
||||
CocoaGraphicsPipe(CGDirectDisplayID display = CGMainDisplayID());
|
||||
virtual ~CocoaGraphicsPipe();
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
* A tiny specialization on GLGraphicsStateGuardian to add some Cocoa-specific
|
||||
* information.
|
||||
*/
|
||||
class CocoaGraphicsStateGuardian : public GLGraphicsStateGuardian {
|
||||
class EXPCL_PANDA_COCOADISPLAY CocoaGraphicsStateGuardian : public GLGraphicsStateGuardian {
|
||||
public:
|
||||
INLINE const FrameBufferProperties &get_fb_properties() const;
|
||||
void get_properties(FrameBufferProperties &properties,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
* An interface to the Cocoa system for managing OpenGL windows under Mac OS
|
||||
* X.
|
||||
*/
|
||||
class CocoaGraphicsWindow : public GraphicsWindow {
|
||||
class EXPCL_PANDA_COCOADISPLAY CocoaGraphicsWindow : public GraphicsWindow {
|
||||
public:
|
||||
CocoaGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
|
||||
const std::string &name,
|
||||
|
|
|
|||
|
|
@ -1618,7 +1618,7 @@ handle_key_event(NSEvent *event) {
|
|||
if ([event type] == NSKeyDown) {
|
||||
// Translate it to a unicode character for keystrokes. I would use
|
||||
// interpretKeyEvents and insertText, but that doesn't handle dead keys.
|
||||
TISInputSourceRef input_source = TISCopyCurrentKeyboardInputSource();
|
||||
TISInputSourceRef input_source = TISCopyCurrentKeyboardLayoutInputSource();
|
||||
CFDataRef layout_data = (CFDataRef)TISGetInputSourceProperty(input_source, kTISPropertyUnicodeKeyLayoutData);
|
||||
const UCKeyboardLayout *layout = (const UCKeyboardLayout *)CFDataGetBytePtr(layout_data);
|
||||
|
||||
|
|
@ -1827,7 +1827,7 @@ get_keyboard_map() const {
|
|||
const UCKeyboardLayout *layout;
|
||||
|
||||
// Get the current keyboard layout data.
|
||||
input_source = TISCopyCurrentKeyboardInputSource();
|
||||
input_source = TISCopyCurrentKeyboardLayoutInputSource();
|
||||
layout_data = (CFDataRef) TISGetInputSourceProperty(input_source, kTISPropertyUnicodeKeyLayoutData);
|
||||
layout = (const UCKeyboardLayout *)CFDataGetBytePtr(layout_data);
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ PUBLISHED:
|
|||
|
||||
INLINE void set_respect_prev_transform(bool flag);
|
||||
INLINE bool get_respect_prev_transform() const;
|
||||
MAKE_PROPERTY(respect_preV_transform, get_respect_prev_transform,
|
||||
MAKE_PROPERTY(respect_prev_transform, get_respect_prev_transform,
|
||||
set_respect_prev_transform);
|
||||
|
||||
void add_collider(const NodePath &collider, CollisionHandler *handler);
|
||||
|
|
|
|||
|
|
@ -62,6 +62,15 @@ enum QuirkBits {
|
|||
// We only connect it if it is reporting any events, because when Steam is
|
||||
// running, the Steam controller is muted in favour of a dummy Xbox device.
|
||||
QB_steam_controller = 32,
|
||||
|
||||
// Axes on the right stick are swapped, using x for y and vice versa.
|
||||
QB_right_axes_swapped = 64,
|
||||
|
||||
// Has no trigger axes.
|
||||
QB_no_analog_triggers = 128,
|
||||
|
||||
// Alternate button mapping.
|
||||
QB_alt_button_mapping = 256,
|
||||
};
|
||||
|
||||
static const struct DeviceMapping {
|
||||
|
|
@ -81,11 +90,15 @@ static const struct DeviceMapping {
|
|||
// Steam Controller (wireless)
|
||||
{0x28de, 0x1142, InputDevice::DeviceClass::unknown, QB_steam_controller},
|
||||
// Jess Tech Colour Rumble Pad
|
||||
{0x0f30, 0x0111, InputDevice::DeviceClass::gamepad, 0},
|
||||
// SPEED Link SL-6535-SBK-01
|
||||
{0x0079, 0x0006, InputDevice::DeviceClass::gamepad, 0},
|
||||
{0x0f30, 0x0111, InputDevice::DeviceClass::gamepad, QB_rstick_from_z | QB_right_axes_swapped},
|
||||
// Trust GXT 24
|
||||
{0x0079, 0x0006, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers | QB_alt_button_mapping},
|
||||
// 8bitdo N30 Pro Controller
|
||||
{0x2dc8, 0x9001, InputDevice::DeviceClass::gamepad, QB_rstick_from_z},
|
||||
// Generic gamepad
|
||||
{0x0810, 0x0001, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers | QB_alt_button_mapping | QB_rstick_from_z | QB_right_axes_swapped},
|
||||
// Generic gamepad without sticks
|
||||
{0x0810, 0xe501, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers | QB_alt_button_mapping},
|
||||
// 3Dconnexion Space Traveller 3D Mouse
|
||||
{0x046d, 0xc623, InputDevice::DeviceClass::spatial_mouse, 0},
|
||||
// 3Dconnexion Space Pilot 3D Mouse
|
||||
|
|
@ -488,10 +501,16 @@ init_device() {
|
|||
break;
|
||||
case ABS_Z:
|
||||
if (quirks & QB_rstick_from_z) {
|
||||
axis = InputDevice::Axis::right_x;
|
||||
if (quirks & QB_right_axes_swapped) {
|
||||
axis = InputDevice::Axis::right_y;
|
||||
} else {
|
||||
axis = InputDevice::Axis::right_x;
|
||||
}
|
||||
} else if (_device_class == DeviceClass::gamepad) {
|
||||
axis = InputDevice::Axis::left_trigger;
|
||||
have_analog_triggers = true;
|
||||
if ((quirks & QB_no_analog_triggers) == 0) {
|
||||
axis = InputDevice::Axis::left_trigger;
|
||||
have_analog_triggers = true;
|
||||
}
|
||||
} else if (_device_class == DeviceClass::spatial_mouse) {
|
||||
axis = InputDevice::Axis::z;
|
||||
} else {
|
||||
|
|
@ -514,10 +533,19 @@ init_device() {
|
|||
break;
|
||||
case ABS_RZ:
|
||||
if (quirks & QB_rstick_from_z) {
|
||||
axis = InputDevice::Axis::right_y;
|
||||
if (quirks & QB_right_axes_swapped) {
|
||||
axis = InputDevice::Axis::right_x;
|
||||
} else {
|
||||
axis = InputDevice::Axis::right_y;
|
||||
}
|
||||
} else if (_device_class == DeviceClass::gamepad) {
|
||||
axis = InputDevice::Axis::right_trigger;
|
||||
have_analog_triggers = true;
|
||||
if ((quirks & QB_no_analog_triggers) == 0) {
|
||||
axis = InputDevice::Axis::right_trigger;
|
||||
have_analog_triggers = true;
|
||||
} else {
|
||||
// Special weird case for Trust GXT 24
|
||||
axis = InputDevice::Axis::right_y;
|
||||
}
|
||||
} else {
|
||||
axis = InputDevice::Axis::yaw;
|
||||
}
|
||||
|
|
@ -537,8 +565,10 @@ init_device() {
|
|||
break;
|
||||
case ABS_GAS:
|
||||
if (_device_class == DeviceClass::gamepad) {
|
||||
axis = InputDevice::Axis::right_trigger;
|
||||
have_analog_triggers = true;
|
||||
if ((quirks & QB_no_analog_triggers) == 0) {
|
||||
axis = InputDevice::Axis::right_trigger;
|
||||
have_analog_triggers = true;
|
||||
}
|
||||
} else {
|
||||
axis = InputDevice::Axis::accelerator;
|
||||
}
|
||||
|
|
@ -963,6 +993,26 @@ map_button(int code, DeviceClass device_class, int quirks) {
|
|||
// BTN_THUMB and BTN_THUMB2 detect touching the touchpads.
|
||||
return ButtonHandle::none();
|
||||
|
||||
} else if (device_class == DeviceClass::gamepad &&
|
||||
(quirks & QB_alt_button_mapping) != 0) {
|
||||
static const ButtonHandle mapping[] = {
|
||||
GamepadButton::face_y(),
|
||||
GamepadButton::face_b(),
|
||||
GamepadButton::face_a(),
|
||||
GamepadButton::face_x(),
|
||||
GamepadButton::lshoulder(),
|
||||
GamepadButton::rshoulder(),
|
||||
GamepadButton::ltrigger(),
|
||||
GamepadButton::rtrigger(),
|
||||
GamepadButton::back(),
|
||||
GamepadButton::start(),
|
||||
GamepadButton::lstick(),
|
||||
GamepadButton::rstick(),
|
||||
};
|
||||
if ((code & 0xf) < 12) {
|
||||
return mapping[code & 0xf];
|
||||
}
|
||||
|
||||
} else if (device_class == DeviceClass::gamepad) {
|
||||
// Based on "Jess Tech Colour Rumble Pad"
|
||||
static const ButtonHandle mapping[] = {
|
||||
|
|
|
|||
|
|
@ -22,8 +22,11 @@
|
|||
#include "mouseButton.h"
|
||||
|
||||
static void removal_callback(void *ctx, IOReturn result, void *sender) {
|
||||
IOKitInputDevice *input_device = (IOKitInputDevice *)ctx;
|
||||
// We need to hold a reference to this because it may otherwise be destroyed
|
||||
// during the call to on_remove().
|
||||
PT(IOKitInputDevice) input_device = (IOKitInputDevice *)ctx;
|
||||
nassertv(input_device != nullptr);
|
||||
nassertv(input_device->test_ref_count_integrity());
|
||||
input_device->on_remove();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,9 @@ LinuxInputDeviceManager() {
|
|||
|
||||
// We'll want to sort the devices by index, since the order may be
|
||||
// meaningful (eg. for the Xbox wireless receiver).
|
||||
if (indices.empty()) {
|
||||
return;
|
||||
}
|
||||
std::sort(indices.begin(), indices.end());
|
||||
_evdev_devices.resize(indices.back() + 1, nullptr);
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,12 @@ enum QuirkBits : int {
|
|||
|
||||
// Throttle is reversed.
|
||||
QB_reversed_throttle = 4,
|
||||
|
||||
// Right stick uses Z and Rz inputs.
|
||||
QB_rstick_from_z = 8,
|
||||
|
||||
// Axes on the right stick are swapped, using x for y and vice versa.
|
||||
QB_right_axes_swapped = 64,
|
||||
};
|
||||
|
||||
// Some nonstandard gamepads have different button mappings.
|
||||
|
|
@ -42,12 +48,17 @@ static const struct DeviceMapping {
|
|||
int quirks;
|
||||
const char *buttons[16];
|
||||
} mapping_presets[] = {
|
||||
// SNES-style USB gamepad
|
||||
// SNES-style USB gamepad, or cheap unbranded USB gamepad with no sticks
|
||||
// ABXY are mapped based on their position, not based on their label.
|
||||
{0x0810, 0xe501, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers,
|
||||
{"face_x", "face_a", "face_b", "face_y", "lshoulder", "rshoulder", "none", "none", "back", "start"}
|
||||
{"face_y", "face_b", "face_a", "face_x", "lshoulder", "rshoulder", "ltrigger", "rtrigger", "back", "start"}
|
||||
},
|
||||
// SPEED Link SL-6535-SBK-01
|
||||
{0x0079, 0x0006, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers,
|
||||
// Unbranded generic cheap USB gamepad
|
||||
{0x0810, 0x0001, InputDevice::DeviceClass::gamepad, QB_rstick_from_z | QB_no_analog_triggers | QB_right_axes_swapped,
|
||||
{"face_y", "face_b", "face_a", "face_x", "lshoulder", "rshoulder", "ltrigger", "rtrigger", "back", "start", "lstick", "rstick"}
|
||||
},
|
||||
// Trust GXT 24 / SPEED Link SL-6535-SBK-01
|
||||
{0x0079, 0x0006, InputDevice::DeviceClass::gamepad, QB_rstick_from_z | QB_no_analog_triggers,
|
||||
{"face_y", "face_b", "face_a", "face_x", "lshoulder", "rshoulder", "ltrigger", "rtrigger", "back", "start", "lstick", "rstick"}
|
||||
},
|
||||
// T.Flight Hotas X
|
||||
|
|
@ -56,7 +67,7 @@ static const struct DeviceMapping {
|
|||
},
|
||||
// NVIDIA Shield Controller
|
||||
{0x0955, 0x7214, InputDevice::DeviceClass::gamepad, 0,
|
||||
{"face_a", "face_b", "n", "face_x", "face_y", "rshoulder", "lshoulder", "rshoulder", "e", "f", "g", "start", "h", "lstick", "rstick", "i"}
|
||||
{"face_a", "face_b", 0, "face_x", "face_y", "rshoulder", "lshoulder", "rshoulder", 0, 0, 0, "start", 0, "lstick", "rstick", 0}
|
||||
},
|
||||
{0},
|
||||
};
|
||||
|
|
@ -422,7 +433,14 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) {
|
|||
break;
|
||||
case HID_USAGE_GENERIC_Z:
|
||||
if (_device_class == DeviceClass::gamepad) {
|
||||
if ((quirks & QB_no_analog_triggers) == 0) {
|
||||
if (quirks & QB_rstick_from_z) {
|
||||
if (quirks & QB_right_axes_swapped) {
|
||||
axis = InputDevice::Axis::right_y;
|
||||
swap(cap.LogicalMin, cap.LogicalMax);
|
||||
} else {
|
||||
axis = InputDevice::Axis::right_x;
|
||||
}
|
||||
} else if ((quirks & QB_no_analog_triggers) == 0) {
|
||||
axis = Axis::left_trigger;
|
||||
}
|
||||
} else if (_device_class == DeviceClass::flight_stick) {
|
||||
|
|
@ -455,7 +473,14 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) {
|
|||
break;
|
||||
case HID_USAGE_GENERIC_RZ:
|
||||
if (_device_class == DeviceClass::gamepad) {
|
||||
if ((quirks & QB_no_analog_triggers) == 0) {
|
||||
if (quirks & QB_rstick_from_z) {
|
||||
if (quirks & QB_right_axes_swapped) {
|
||||
axis = InputDevice::Axis::right_x;
|
||||
} else {
|
||||
axis = InputDevice::Axis::right_y;
|
||||
swap(cap.LogicalMin, cap.LogicalMax);
|
||||
}
|
||||
} else if ((quirks & QB_no_analog_triggers) == 0) {
|
||||
axis = Axis::right_trigger;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -481,6 +506,16 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) {
|
|||
break;
|
||||
}
|
||||
|
||||
// If this axis already exists, don't double-map it, but take the first
|
||||
// one. This is important for the Trust GXT 24 / SL-6535-SBK-01 which
|
||||
// have a weird extra Z axis with DataIndex 2 that should be ignored.
|
||||
for (size_t i = 0; i < _axes.size(); ++i) {
|
||||
if (_axes[i].axis == axis) {
|
||||
axis = Axis::none;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int axis_index;
|
||||
if (!is_signed) {
|
||||
// All axes on the weird XInput-style mappings go from -1 to 1
|
||||
|
|
|
|||
|
|
@ -1901,6 +1901,14 @@ fetch_ptr_parameter(const Shader::ShaderPtrSpec& spec) {
|
|||
return (_target_shader->get_shader_input_ptr(spec._arg));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
bool GraphicsStateGuardian::
|
||||
fetch_ptr_parameter(const Shader::ShaderPtrSpec& spec, Shader::ShaderPtrData &data) {
|
||||
return _target_shader->get_shader_input_ptr(spec._arg, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the specified DisplayRegion current. All future drawing and clear
|
||||
* operations will be constrained within the given DisplayRegion.
|
||||
|
|
|
|||
|
|
@ -339,6 +339,7 @@ public:
|
|||
PT(Texture) fetch_specified_texture(Shader::ShaderTexSpec &spec,
|
||||
SamplerState &sampler, int &view);
|
||||
const Shader::ShaderPtrData *fetch_ptr_parameter(const Shader::ShaderPtrSpec& spec);
|
||||
bool fetch_ptr_parameter(const Shader::ShaderPtrSpec &spec, Shader::ShaderPtrData &data);
|
||||
|
||||
virtual void prepare_display_region(DisplayRegionPipelineReader *dr);
|
||||
virtual void clear_before_callback();
|
||||
|
|
|
|||
|
|
@ -2068,10 +2068,7 @@ open_read_subfile(Subfile *subfile) {
|
|||
stream = wrapper;
|
||||
|
||||
// Validate the password by confirming that the encryption header matches.
|
||||
char this_header[_encrypt_header_size];
|
||||
stream->read(this_header, _encrypt_header_size);
|
||||
if (stream->fail() || stream->gcount() != (unsigned)_encrypt_header_size ||
|
||||
memcmp(this_header, _encrypt_header, _encrypt_header_size) != 0) {
|
||||
if (!wrapper->read_magic(_encrypt_header, _encrypt_header_size)) {
|
||||
express_cat.error()
|
||||
<< "Unable to decrypt subfile " << subfile->_name << ".\n";
|
||||
delete stream;
|
||||
|
|
|
|||
|
|
@ -202,8 +202,8 @@ seekoff(streamoff off, ios_seekdir dir, ios_openmode which) {
|
|||
|
||||
gbump(n);
|
||||
|
||||
_source->seekg(0, ios::beg);
|
||||
if (_source->tellg() == (streampos)0) {
|
||||
if (_source->rdbuf()->pubseekpos(0, ios::in) == (streampos)0) {
|
||||
_source->clear();
|
||||
_z_source.next_in = Z_NULL;
|
||||
_z_source.avail_in = 0;
|
||||
_z_source.next_out = Z_NULL;
|
||||
|
|
|
|||
|
|
@ -462,7 +462,7 @@ seek(double t) {
|
|||
* read. Your buffer must be equal in size to N * channels. Multiple-channel
|
||||
* audio will be interleaved.
|
||||
*/
|
||||
void FfmpegAudioCursor::
|
||||
int FfmpegAudioCursor::
|
||||
read_samples(int n, int16_t *data) {
|
||||
int desired = n * _audio_channels;
|
||||
|
||||
|
|
@ -486,4 +486,5 @@ read_samples(int n, int16_t *data) {
|
|||
|
||||
}
|
||||
_samples_read += n;
|
||||
return n;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ PUBLISHED:
|
|||
virtual void seek(double offset);
|
||||
|
||||
public:
|
||||
virtual void read_samples(int n, int16_t *data);
|
||||
virtual int read_samples(int n, int16_t *data);
|
||||
|
||||
protected:
|
||||
void fetch_packet();
|
||||
|
|
|
|||
|
|
@ -755,7 +755,9 @@ do_poll() {
|
|||
PT(FfmpegBuffer) frame = do_alloc_frame();
|
||||
nassertr(frame != nullptr, false);
|
||||
_lock.release();
|
||||
advance_to_frame(seek_frame);
|
||||
if (seek_frame != _begin_frame) {
|
||||
advance_to_frame(seek_frame);
|
||||
}
|
||||
if (_frame_ready) {
|
||||
export_frame(frame);
|
||||
_lock.acquire();
|
||||
|
|
|
|||
|
|
@ -597,8 +597,31 @@ reset() {
|
|||
query_glsl_version();
|
||||
|
||||
#ifndef OPENGLES
|
||||
bool core_profile = is_at_least_gl_version(3, 2) &&
|
||||
!has_extension("GL_ARB_compatibility");
|
||||
// Determine whether this OpenGL context has compatibility features.
|
||||
bool core_profile = false;
|
||||
|
||||
if (_gl_version_major >= 3) {
|
||||
if (_gl_version_major > 3 || _gl_version_minor >= 2) {
|
||||
// OpenGL 3.2 has a built-in way to check this.
|
||||
GLint profile_mask = 0;
|
||||
glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &profile_mask);
|
||||
|
||||
if (profile_mask & GL_CONTEXT_CORE_PROFILE_BIT) {
|
||||
core_profile = true;
|
||||
} else if (profile_mask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) {
|
||||
core_profile = false;
|
||||
} else {
|
||||
core_profile = !has_extension("GL_ARB_compatibility");
|
||||
}
|
||||
} else {
|
||||
// OpenGL 3.0/3.1.
|
||||
GLint flags = 0;
|
||||
glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
|
||||
if (flags & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT) {
|
||||
core_profile = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (GLCAT.is_debug()) {
|
||||
if (core_profile) {
|
||||
|
|
|
|||
|
|
@ -2110,8 +2110,8 @@ issue_parameters(int altered) {
|
|||
for (int i = 0; i < (int)_shader->_ptr_spec.size(); ++i) {
|
||||
Shader::ShaderPtrSpec &spec = _shader->_ptr_spec[i];
|
||||
|
||||
const Shader::ShaderPtrData* ptr_data = _glgsg->fetch_ptr_parameter(spec);
|
||||
if (ptr_data == nullptr) { //the input is not contained in ShaderPtrData
|
||||
Shader::ShaderPtrData ptr_data;
|
||||
if (!_glgsg->fetch_ptr_parameter(spec, ptr_data)) { //the input is not contained in ShaderPtrData
|
||||
release_resources();
|
||||
return;
|
||||
}
|
||||
|
|
@ -2119,18 +2119,18 @@ issue_parameters(int altered) {
|
|||
nassertd(spec._dim[1] > 0) continue;
|
||||
|
||||
GLint p = spec._id._seqno;
|
||||
int array_size = min(spec._dim[0], (int)ptr_data->_size / spec._dim[1]);
|
||||
int array_size = min(spec._dim[0], (int)ptr_data._size / spec._dim[1]);
|
||||
switch (spec._type) {
|
||||
case Shader::SPT_float:
|
||||
{
|
||||
float *data = nullptr;
|
||||
|
||||
switch (ptr_data->_type) {
|
||||
switch (ptr_data._type) {
|
||||
case Shader::SPT_int:
|
||||
// Convert int data to float data.
|
||||
data = (float*) alloca(sizeof(float) * array_size * spec._dim[1]);
|
||||
for (int i = 0; i < (array_size * spec._dim[1]); ++i) {
|
||||
data[i] = (float)(((int*)ptr_data->_ptr)[i]);
|
||||
data[i] = (float)(((int*)ptr_data._ptr)[i]);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -2138,7 +2138,7 @@ issue_parameters(int altered) {
|
|||
// Convert unsigned int data to float data.
|
||||
data = (float*) alloca(sizeof(float) * array_size * spec._dim[1]);
|
||||
for (int i = 0; i < (array_size * spec._dim[1]); ++i) {
|
||||
data[i] = (float)(((unsigned int*)ptr_data->_ptr)[i]);
|
||||
data[i] = (float)(((unsigned int*)ptr_data._ptr)[i]);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -2146,12 +2146,12 @@ issue_parameters(int altered) {
|
|||
// Downgrade double data to float data.
|
||||
data = (float*) alloca(sizeof(float) * array_size * spec._dim[1]);
|
||||
for (int i = 0; i < (array_size * spec._dim[1]); ++i) {
|
||||
data[i] = (float)(((double*)ptr_data->_ptr)[i]);
|
||||
data[i] = (float)(((double*)ptr_data._ptr)[i]);
|
||||
}
|
||||
break;
|
||||
|
||||
case Shader::SPT_float:
|
||||
data = (float*)ptr_data->_ptr;
|
||||
data = (float*)ptr_data._ptr;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
@ -2171,8 +2171,8 @@ issue_parameters(int altered) {
|
|||
break;
|
||||
|
||||
case Shader::SPT_int:
|
||||
if (ptr_data->_type != Shader::SPT_int &&
|
||||
ptr_data->_type != Shader::SPT_uint) {
|
||||
if (ptr_data._type != Shader::SPT_int &&
|
||||
ptr_data._type != Shader::SPT_uint) {
|
||||
GLCAT.error()
|
||||
<< "Cannot pass floating-point data to integer shader input '" << spec._id._name << "'\n";
|
||||
|
||||
|
|
@ -2183,18 +2183,18 @@ issue_parameters(int altered) {
|
|||
|
||||
} else {
|
||||
switch (spec._dim[1]) {
|
||||
case 1: _glgsg->_glUniform1iv(p, array_size, (int*)ptr_data->_ptr); continue;
|
||||
case 2: _glgsg->_glUniform2iv(p, array_size, (int*)ptr_data->_ptr); continue;
|
||||
case 3: _glgsg->_glUniform3iv(p, array_size, (int*)ptr_data->_ptr); continue;
|
||||
case 4: _glgsg->_glUniform4iv(p, array_size, (int*)ptr_data->_ptr); continue;
|
||||
case 1: _glgsg->_glUniform1iv(p, array_size, (int*)ptr_data._ptr); continue;
|
||||
case 2: _glgsg->_glUniform2iv(p, array_size, (int*)ptr_data._ptr); continue;
|
||||
case 3: _glgsg->_glUniform3iv(p, array_size, (int*)ptr_data._ptr); continue;
|
||||
case 4: _glgsg->_glUniform4iv(p, array_size, (int*)ptr_data._ptr); continue;
|
||||
}
|
||||
nassertd(false) continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case Shader::SPT_uint:
|
||||
if (ptr_data->_type != Shader::SPT_uint &&
|
||||
ptr_data->_type != Shader::SPT_int) {
|
||||
if (ptr_data._type != Shader::SPT_uint &&
|
||||
ptr_data._type != Shader::SPT_int) {
|
||||
GLCAT.error()
|
||||
<< "Cannot pass floating-point data to integer shader input '" << spec._id._name << "'\n";
|
||||
|
||||
|
|
@ -2205,10 +2205,10 @@ issue_parameters(int altered) {
|
|||
|
||||
} else {
|
||||
switch (spec._dim[1]) {
|
||||
case 1: _glgsg->_glUniform1uiv(p, array_size, (GLuint *)ptr_data->_ptr); continue;
|
||||
case 2: _glgsg->_glUniform2uiv(p, array_size, (GLuint *)ptr_data->_ptr); continue;
|
||||
case 3: _glgsg->_glUniform3uiv(p, array_size, (GLuint *)ptr_data->_ptr); continue;
|
||||
case 4: _glgsg->_glUniform4uiv(p, array_size, (GLuint *)ptr_data->_ptr); continue;
|
||||
case 1: _glgsg->_glUniform1uiv(p, array_size, (GLuint *)ptr_data._ptr); continue;
|
||||
case 2: _glgsg->_glUniform2uiv(p, array_size, (GLuint *)ptr_data._ptr); continue;
|
||||
case 3: _glgsg->_glUniform3uiv(p, array_size, (GLuint *)ptr_data._ptr); continue;
|
||||
case 4: _glgsg->_glUniform4uiv(p, array_size, (GLuint *)ptr_data._ptr); continue;
|
||||
}
|
||||
nassertd(false) continue;
|
||||
}
|
||||
|
|
@ -3206,6 +3206,10 @@ glsl_compile_and_link() {
|
|||
valid &= glsl_compile_shader(Shader::ST_compute);
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// There might be warnings, so report those. GLSLShaders::const_iterator
|
||||
// it; for (it = _glsl_shaders.begin(); it != _glsl_shaders.end(); ++it) {
|
||||
// glsl_report_shader_errors(*it); }
|
||||
|
|
|
|||
|
|
@ -17,6 +17,11 @@ ConfigVariableInt gl_version
|
|||
("gl-version", "",
|
||||
PRC_DESC("Set this to get an OpenGL context with a specific version."));
|
||||
|
||||
ConfigVariableBool gl_forward_compatible
|
||||
("gl-forward-compatible", false,
|
||||
PRC_DESC("Setting this to true will request a forward-compatible OpenGL "
|
||||
"context, which will not support the fixed-function pipeline."));
|
||||
|
||||
ConfigVariableBool gl_support_fbo
|
||||
("gl-support-fbo", true,
|
||||
PRC_DESC("Configure this false if your GL's implementation of "
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
// #define GSG_VERBOSE 1
|
||||
|
||||
extern EXPCL_GL ConfigVariableInt gl_version;
|
||||
extern EXPCL_GL ConfigVariableBool gl_forward_compatible;
|
||||
extern EXPCL_GL ConfigVariableBool gl_support_fbo;
|
||||
extern ConfigVariableBool gl_cheap_textures;
|
||||
extern ConfigVariableBool gl_ignore_clamp;
|
||||
|
|
|
|||
|
|
@ -352,9 +352,20 @@ choose_pixel_format(const FrameBufferProperties &properties,
|
|||
attrib_list[n++] = gl_version[1];
|
||||
}
|
||||
}
|
||||
int flags = 0;
|
||||
if (gl_debug) {
|
||||
flags |= GLX_CONTEXT_DEBUG_BIT_ARB;
|
||||
}
|
||||
if (gl_forward_compatible) {
|
||||
flags |= GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB;
|
||||
if (gl_version.get_num_words() == 0 || gl_version[0] < 2) {
|
||||
glxdisplay_cat.error()
|
||||
<< "gl-forward-compatible requires gl-version >= 3 0\n";
|
||||
}
|
||||
}
|
||||
if (flags != 0) {
|
||||
attrib_list[n++] = GLX_CONTEXT_FLAGS_ARB;
|
||||
attrib_list[n++] = GLX_CONTEXT_DEBUG_BIT_ARB;
|
||||
attrib_list[n++] = flags;
|
||||
}
|
||||
attrib_list[n] = None;
|
||||
_context = _glXCreateContextAttribs(_display, _fbconfig, _share_context,
|
||||
|
|
|
|||
|
|
@ -484,13 +484,9 @@ get_col(int col) const {
|
|||
*/
|
||||
INLINE_LINMATH FLOATNAME(LVecBase3) FLOATNAME(LMatrix4)::
|
||||
get_row3(int row) const {
|
||||
#ifdef HAVE_EIGEN
|
||||
return FLOATNAME(LVecBase3)(_m.block<1, 3>(row, 0));
|
||||
#else
|
||||
return FLOATNAME(LVecBase3)((*this)(row, 0),
|
||||
(*this)(row, 1),
|
||||
(*this)(row, 2));
|
||||
#endif // HAVE_EIGEN
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -514,13 +510,9 @@ get_row3(FLOATNAME(LVecBase3) &result_vec,int row) const {
|
|||
*/
|
||||
INLINE_LINMATH FLOATNAME(LVecBase3) FLOATNAME(LMatrix4)::
|
||||
get_col3(int col) const {
|
||||
#ifdef HAVE_EIGEN
|
||||
return FLOATNAME(LVecBase3)(_m.block<1, 3>(0, col));
|
||||
#else
|
||||
return FLOATNAME(LVecBase3)((*this)(0, col),
|
||||
(*this)(1, col),
|
||||
(*this)(2, col));
|
||||
#endif // HAVE_EIGEN
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@
|
|||
*/
|
||||
double PerlinNoise2::
|
||||
noise(const LVecBase2d &value) const {
|
||||
// If this triggers, you passed in 0 for table_size.
|
||||
nassertr(!_index.empty(), make_nan(0.0));
|
||||
|
||||
// Convert the vector to our local coordinate space.
|
||||
LVecBase2d vec = _input_xform.xform_point(value);
|
||||
|
||||
|
|
@ -41,9 +44,13 @@ noise(const LVecBase2d &value) const {
|
|||
double v = fade(y);
|
||||
|
||||
// Hash coordinates of the 4 square corners (A, B, A + 1, and B + 1)
|
||||
nassertr(X >= 0 && X + 1 < _index.size(), make_nan(0.0));
|
||||
int A = _index[X] + Y;
|
||||
int B = _index[X + 1] + Y;
|
||||
|
||||
nassertr(A >= 0 && A + 1 < _index.size(), make_nan(0.0));
|
||||
nassertr(B >= 0 && B + 1 < _index.size(), make_nan(0.0));
|
||||
|
||||
// and add blended results from 4 corners of square.
|
||||
double result =
|
||||
lerp(v, lerp(u, grad(_index[A], x, y),
|
||||
|
|
|
|||
|
|
@ -118,8 +118,10 @@ seek(double t) {
|
|||
* read. Your buffer must be equal in size to N * channels. Multiple-channel
|
||||
* audio will be interleaved.
|
||||
*/
|
||||
void FlacAudioCursor::
|
||||
int FlacAudioCursor::
|
||||
read_samples(int n, int16_t *data) {
|
||||
int desired = n * _audio_channels;
|
||||
_samples_read += drflac_read_s16(_drflac, desired, data) / _audio_channels;
|
||||
n = drflac_read_s16(_drflac, desired, data) / _audio_channels;
|
||||
_samples_read += n;
|
||||
return n;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ PUBLISHED:
|
|||
virtual void seek(double offset);
|
||||
|
||||
public:
|
||||
virtual void read_samples(int n, int16_t *data);
|
||||
virtual int read_samples(int n, int16_t *data);
|
||||
|
||||
bool _is_valid;
|
||||
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ public:
|
|||
int _samples_per_buffer;
|
||||
|
||||
public:
|
||||
virtual void read_samples(int n, int16_t *data);
|
||||
virtual int read_samples(int n, int16_t *data);
|
||||
virtual int ready() const;
|
||||
|
||||
public:
|
||||
|
|
@ -323,7 +323,7 @@ MicrophoneAudioCursorDS::
|
|||
/**
|
||||
*
|
||||
*/
|
||||
void MicrophoneAudioCursorDS::
|
||||
int MicrophoneAudioCursorDS::
|
||||
read_samples(int n, int16_t *data) {
|
||||
int orign = n;
|
||||
if (_handle) {
|
||||
|
|
@ -373,6 +373,7 @@ read_samples(int n, int16_t *data) {
|
|||
if (n > 0) {
|
||||
memcpy(data, 0, n*2*_audio_channels);
|
||||
}
|
||||
return orign - n;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -45,14 +45,14 @@ MovieAudioCursor::
|
|||
* read. Your buffer must be equal in size to N * channels. Multiple-channel
|
||||
* audio will be interleaved.
|
||||
*/
|
||||
void MovieAudioCursor::
|
||||
int MovieAudioCursor::
|
||||
read_samples(int n, int16_t *data) {
|
||||
|
||||
// This is the null implementation, which generates pure silence. Normally,
|
||||
// this method will be overridden by a subclass.
|
||||
|
||||
if (n <= 0) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int desired = n * _audio_channels;
|
||||
|
|
@ -60,6 +60,7 @@ read_samples(int n, int16_t *data) {
|
|||
data[i] = 0;
|
||||
}
|
||||
_samples_read += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -92,23 +93,28 @@ read_samples(int n, Datagram *dg) {
|
|||
* This is not particularly efficient, but it may be a convenient way to
|
||||
* manipulate samples in python.
|
||||
*/
|
||||
std::string MovieAudioCursor::
|
||||
vector_uchar MovieAudioCursor::
|
||||
read_samples(int n) {
|
||||
std::ostringstream result;
|
||||
vector_uchar result;
|
||||
int16_t tmp[4096];
|
||||
while (n > 0) {
|
||||
int blocksize = (4096 / _audio_channels);
|
||||
if (blocksize > n) blocksize = n;
|
||||
int words = blocksize * _audio_channels;
|
||||
read_samples(blocksize, tmp);
|
||||
for (int i=0; i<words; i++) {
|
||||
int16_t word = tmp[i];
|
||||
result.put((char)(word & 255));
|
||||
result.put((char)((word>>8) & 255));
|
||||
if (blocksize > n) {
|
||||
blocksize = n;
|
||||
}
|
||||
n -= blocksize;
|
||||
int nread = read_samples(blocksize, tmp);
|
||||
if (nread == 0) {
|
||||
return result;
|
||||
}
|
||||
int words = nread * _audio_channels;
|
||||
for (int i = 0; i < words; ++i) {
|
||||
int16_t word = tmp[i];
|
||||
result.push_back((uint8_t)(word & 255u));
|
||||
result.push_back((uint8_t)((word >> 8) & 255u));
|
||||
}
|
||||
n -= nread;
|
||||
}
|
||||
return result.str();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -48,10 +48,10 @@ PUBLISHED:
|
|||
virtual int ready() const;
|
||||
virtual void seek(double offset);
|
||||
void read_samples(int n, Datagram *dg);
|
||||
std::string read_samples(int n);
|
||||
vector_uchar read_samples(int n);
|
||||
|
||||
public:
|
||||
virtual void read_samples(int n, int16_t *data);
|
||||
virtual int read_samples(int n, int16_t *data);
|
||||
|
||||
protected:
|
||||
PT(MovieAudio) _source;
|
||||
|
|
|
|||
|
|
@ -31,6 +31,12 @@ PT(MovieAudio) MovieTypeRegistry::
|
|||
make_audio(const Filename &name) {
|
||||
string ext = downcase(name.get_extension());
|
||||
|
||||
#ifdef HAVE_ZLIB
|
||||
if (ext == "pz" || ext == "gz") {
|
||||
ext = Filename(name.get_basename_wo_extension()).get_extension();
|
||||
}
|
||||
#endif
|
||||
|
||||
_audio_lock.lock();
|
||||
|
||||
// Make sure that the list of audio types has been read in.
|
||||
|
|
@ -154,6 +160,12 @@ PT(MovieVideo) MovieTypeRegistry::
|
|||
make_video(const Filename &name) {
|
||||
string ext = downcase(name.get_extension());
|
||||
|
||||
#ifdef HAVE_ZLIB
|
||||
if (ext == "pz" || ext == "gz") {
|
||||
ext = Filename(name.get_basename_wo_extension()).get_extension();
|
||||
}
|
||||
#endif
|
||||
|
||||
_video_lock.lock();
|
||||
|
||||
// Make sure that the list of video types has been read in.
|
||||
|
|
|
|||
|
|
@ -56,6 +56,22 @@ int cb_seek(void *stream, opus_int64 offset, int whence) {
|
|||
break;
|
||||
|
||||
case SEEK_CUR:
|
||||
// opusfile uses a seek with offset 0 to determine whether seeking is
|
||||
// supported, but this is not good enough. We seek to the end and back.
|
||||
if (offset == 0) {
|
||||
std::streambuf *buf = in->rdbuf();
|
||||
std::streampos pos = buf->pubseekoff(0, std::ios::cur, std::ios::in);
|
||||
if (pos < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (buf->pubseekoff(0, std::ios::end, std::ios::in) >= 0) {
|
||||
// It worked; seek back to the previous location.
|
||||
buf->pubseekpos(pos, std::ios::in);
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
in->seekg(offset, std::ios::cur);
|
||||
break;
|
||||
|
||||
|
|
@ -101,16 +117,7 @@ opus_int64 cb_tell(void *stream) {
|
|||
return in->tellg();
|
||||
}
|
||||
|
||||
int cb_close(void *stream) {
|
||||
istream *in = (istream *)stream;
|
||||
nassertr(in != nullptr, EOF);
|
||||
|
||||
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
|
||||
vfs->close_read_file(in);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const OpusFileCallbacks callbacks = {cb_read, cb_seek, cb_tell, cb_close};
|
||||
static const OpusFileCallbacks callbacks = {cb_read, cb_seek, cb_tell, nullptr};
|
||||
|
||||
TypeHandle OpusAudioCursor::_type_handle;
|
||||
|
||||
|
|
@ -122,6 +129,7 @@ OpusAudioCursor::
|
|||
OpusAudioCursor(OpusAudio *src, istream *stream) :
|
||||
MovieAudioCursor(src),
|
||||
_is_valid(false),
|
||||
_stream(stream),
|
||||
_link(0)
|
||||
{
|
||||
nassertv(stream != nullptr);
|
||||
|
|
@ -159,6 +167,11 @@ OpusAudioCursor::
|
|||
op_free(_op);
|
||||
_op = nullptr;
|
||||
}
|
||||
|
||||
if (_stream != nullptr) {
|
||||
VirtualFileSystem::close_read_file(_stream);
|
||||
_stream = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -174,7 +187,32 @@ seek(double t) {
|
|||
t = std::max(t, 0.0);
|
||||
|
||||
// Use op_time_seek_lap if cross-lapping is enabled.
|
||||
int error = op_pcm_seek(_op, (ogg_int64_t)(t * 48000.0));
|
||||
ogg_int64_t sample = (ogg_int64_t)(t * 48000.0);
|
||||
int error = op_pcm_seek(_op, sample);
|
||||
|
||||
// Special case for seeking to the beginning; if normal seek fails, we may
|
||||
// be able to explicitly seek to the beginning of the file and call op_open
|
||||
// again. This allows looping compressed .opus files.
|
||||
if (error == OP_ENOSEEK && sample == 0) {
|
||||
if (_stream->rdbuf()->pubseekpos(0, std::ios::in) == (std::streampos)0) {
|
||||
OggOpusFile *op = op_open_callbacks((void *)_stream, &callbacks, nullptr, 0, nullptr);
|
||||
if (op != nullptr) {
|
||||
op_free(_op);
|
||||
_op = op;
|
||||
} else {
|
||||
movies_cat.error()
|
||||
<< "Failed to reopen Opus file to seek to beginning.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset this field for good measure, just in case this changed.
|
||||
_audio_channels = op_channel_count(_op, -1);
|
||||
|
||||
_last_seek = 0.0;
|
||||
_samples_read = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (error != 0) {
|
||||
movies_cat.error()
|
||||
<< "Seek failed (error " << error << "). Opus stream may not be seekable.\n";
|
||||
|
|
@ -190,7 +228,7 @@ seek(double t) {
|
|||
* read. Your buffer must be equal in size to N * channels. Multiple-channel
|
||||
* audio will be interleaved.
|
||||
*/
|
||||
void OpusAudioCursor::
|
||||
int OpusAudioCursor::
|
||||
read_samples(int n, int16_t *data) {
|
||||
int16_t *end = data + (n * _audio_channels);
|
||||
|
||||
|
|
@ -202,6 +240,9 @@ read_samples(int n, int16_t *data) {
|
|||
data += read_samples * _audio_channels;
|
||||
_samples_read += read_samples;
|
||||
} else {
|
||||
if (read_samples == 0 && _length == 1.0E10) {
|
||||
_length = op_pcm_tell(_op) / 48000.0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -224,7 +265,9 @@ read_samples(int n, int16_t *data) {
|
|||
// Fill the rest of the buffer with silence.
|
||||
if (data < end) {
|
||||
memset(data, 0, (unsigned char *)end - (unsigned char *)data);
|
||||
n -= (end - data) / _audio_channels;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
#endif // HAVE_OPUS
|
||||
|
|
|
|||
|
|
@ -39,22 +39,14 @@ PUBLISHED:
|
|||
virtual void seek(double offset);
|
||||
|
||||
public:
|
||||
virtual void read_samples(int n, int16_t *data);
|
||||
virtual int read_samples(int n, int16_t *data);
|
||||
|
||||
bool _is_valid;
|
||||
|
||||
protected:
|
||||
OggOpusFile *_op;
|
||||
|
||||
std::istream *_stream;
|
||||
int _link;
|
||||
double _byte_rate;
|
||||
int _block_align;
|
||||
int _bytes_per_sample;
|
||||
bool _is_float;
|
||||
|
||||
std::streampos _data_start;
|
||||
std::streampos _data_pos;
|
||||
size_t _data_size;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
|
|
|
|||
|
|
@ -57,12 +57,14 @@ open() {
|
|||
* read. Your buffer must be equal in size to N * channels. Multiple-channel
|
||||
* audio will be interleaved.
|
||||
*/
|
||||
void UserDataAudio::
|
||||
int UserDataAudio::
|
||||
read_samples(int n, int16_t *data) {
|
||||
int ready = (_data.size() / _desired_channels);
|
||||
int desired = n * _desired_channels;
|
||||
int avail = ready * _desired_channels;
|
||||
if (avail > desired) avail = desired;
|
||||
if (n > ready) {
|
||||
n = ready;
|
||||
}
|
||||
int avail = n * _desired_channels;
|
||||
for (int i=0; i<avail; i++) {
|
||||
data[i] = _data[i];
|
||||
}
|
||||
|
|
@ -72,6 +74,7 @@ read_samples(int n, int16_t *data) {
|
|||
for (int i=0; i<avail; i++) {
|
||||
_data.pop_front();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -107,11 +110,11 @@ append(DatagramIterator *src, int n) {
|
|||
* but it may be convenient to deal with samples in python.
|
||||
*/
|
||||
void UserDataAudio::
|
||||
append(const std::string &str) {
|
||||
append(const vector_uchar &str) {
|
||||
nassertv(!_aborted);
|
||||
int samples = str.size() / (2 * _desired_channels);
|
||||
int words = samples * _desired_channels;
|
||||
for (int i=0; i<words; i++) {
|
||||
for (int i = 0; i < words; ++i) {
|
||||
int c1 = ((unsigned char)str[i*2+0]);
|
||||
int c2 = ((unsigned char)str[i*2+1]);
|
||||
int16_t n = (c1 | (c2 << 8));
|
||||
|
|
|
|||
|
|
@ -37,11 +37,11 @@ class EXPCL_PANDA_MOVIES UserDataAudio : public MovieAudio {
|
|||
|
||||
void append(int16_t *data, int n);
|
||||
void append(DatagramIterator *src, int len=0x40000000);
|
||||
void append(const std::string &str);
|
||||
void append(const vector_uchar &);
|
||||
void done(); // A promise not to write any more samples.
|
||||
|
||||
private:
|
||||
void read_samples(int n, int16_t *data);
|
||||
int read_samples(int n, int16_t *data);
|
||||
void update_cursor();
|
||||
int _desired_rate;
|
||||
int _desired_channels;
|
||||
|
|
|
|||
|
|
@ -47,12 +47,12 @@ UserDataAudioCursor::
|
|||
* read. Your buffer must be equal in size to N * channels. Multiple-channel
|
||||
* audio will be interleaved.
|
||||
*/
|
||||
void UserDataAudioCursor::
|
||||
int UserDataAudioCursor::
|
||||
read_samples(int n, int16_t *data) {
|
||||
UserDataAudio *source = (UserDataAudio*)(MovieAudio*)_source;
|
||||
|
||||
if(source->_remove_after_read) {
|
||||
source->read_samples(n, data);
|
||||
if (source->_remove_after_read) {
|
||||
n = source->read_samples(n, data);
|
||||
}
|
||||
else {
|
||||
int offset = _samples_read * _audio_channels;
|
||||
|
|
@ -66,9 +66,12 @@ read_samples(int n, int16_t *data) {
|
|||
for (int i=avail; i<desired; i++) {
|
||||
data[i] = 0;
|
||||
}
|
||||
|
||||
n = avail / _audio_channels;
|
||||
}
|
||||
|
||||
_samples_read += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ PUBLISHED:
|
|||
virtual ~UserDataAudioCursor();
|
||||
|
||||
public:
|
||||
virtual void read_samples(int n, int16_t *data);
|
||||
virtual int read_samples(int n, int16_t *data);
|
||||
virtual int ready() const;
|
||||
virtual void seek(double offset);
|
||||
|
||||
|
|
|
|||
|
|
@ -91,19 +91,46 @@ seek(double t) {
|
|||
t = std::max(t, 0.0);
|
||||
|
||||
// Use ov_time_seek_lap if cross-lapping is enabled.
|
||||
int result;
|
||||
if (vorbis_seek_lap) {
|
||||
if (ov_time_seek_lap(&_ov, t) != 0) {
|
||||
movies_cat.error()
|
||||
<< "Seek failed. Ogg Vorbis stream may not be seekable.\n";
|
||||
return;
|
||||
}
|
||||
result = ov_time_seek_lap(&_ov, t);
|
||||
} else {
|
||||
if (ov_time_seek(&_ov, t) != 0) {
|
||||
movies_cat.error()
|
||||
<< "Seek failed. Ogg Vorbis stream may not be seekable.\n";
|
||||
result = ov_time_seek(&_ov, t);
|
||||
}
|
||||
|
||||
// Special case for seeking to the beginning; if normal seek fails, we may
|
||||
// be able to explicitly seek to the beginning of the file and call ov_open
|
||||
// again. This allows looping compressed .ogg files.
|
||||
if (result == OV_ENOSEEK && t == 0.0) {
|
||||
std::istream *stream = (std::istream *)_ov.datasource;
|
||||
|
||||
if (stream->rdbuf()->pubseekpos(0, std::ios::in) == (std::streampos)0) {
|
||||
// Back up the callbacks, then destroy the stream, making sure to first
|
||||
// unset the datasource so that it won't close the file.
|
||||
ov_callbacks callbacks = _ov.callbacks;
|
||||
_ov.datasource = nullptr;
|
||||
ov_clear(&_ov);
|
||||
|
||||
if (ov_open_callbacks((void *)stream, &_ov, nullptr, 0, callbacks) != 0) {
|
||||
movies_cat.error()
|
||||
<< "Failed to reopen Ogg Vorbis file to seek to beginning.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset these fields for good measure, just in case the file changed.
|
||||
vorbis_info *vi = ov_info(&_ov, -1);
|
||||
_audio_channels = vi->channels;
|
||||
_audio_rate = vi->rate;
|
||||
|
||||
_last_seek = 0.0;
|
||||
_samples_read = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (result != 0) {
|
||||
movies_cat.error()
|
||||
<< "Seek failed. Ogg Vorbis stream may not be seekable.\n";
|
||||
}
|
||||
|
||||
_last_seek = ov_time_tell(&_ov);
|
||||
_samples_read = 0;
|
||||
|
|
@ -114,7 +141,7 @@ seek(double t) {
|
|||
* read. Your buffer must be equal in size to N * channels. Multiple-channel
|
||||
* audio will be interleaved.
|
||||
*/
|
||||
void VorbisAudioCursor::
|
||||
int VorbisAudioCursor::
|
||||
read_samples(int n, int16_t *data) {
|
||||
int desired = n * _audio_channels;
|
||||
|
||||
|
|
@ -131,6 +158,9 @@ read_samples(int n, int16_t *data) {
|
|||
buffer += read_bytes;
|
||||
length -= read_bytes;
|
||||
} else {
|
||||
if (read_bytes == 0 && _length == 1.0E10) {
|
||||
_length = ov_time_tell(&_ov);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -159,6 +189,7 @@ read_samples(int n, int16_t *data) {
|
|||
}
|
||||
|
||||
_samples_read += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -199,6 +230,22 @@ cb_seek_func(void *datasource, ogg_int64_t offset, int whence) {
|
|||
break;
|
||||
|
||||
case SEEK_CUR:
|
||||
// Vorbis uses a seek with offset 0 to determine whether seeking is
|
||||
// supported, but this is not good enough. We seek to the end and back.
|
||||
if (offset == 0) {
|
||||
std::streambuf *buf = stream->rdbuf();
|
||||
std::streampos pos = buf->pubseekoff(0, std::ios::cur, std::ios::in);
|
||||
if (pos < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (buf->pubseekoff(0, std::ios::end, std::ios::in) >= 0) {
|
||||
// It worked; seek back to the previous location.
|
||||
buf->pubseekpos(pos, std::ios::in);
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
stream->seekg(offset, std::ios::cur);
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ PUBLISHED:
|
|||
virtual void seek(double offset);
|
||||
|
||||
public:
|
||||
virtual void read_samples(int n, int16_t *data);
|
||||
virtual int read_samples(int n, int16_t *data);
|
||||
|
||||
bool _is_valid;
|
||||
|
||||
|
|
@ -50,16 +50,7 @@ protected:
|
|||
#ifndef CPPPARSER
|
||||
OggVorbis_File _ov;
|
||||
#endif
|
||||
|
||||
int _bitstream;
|
||||
double _byte_rate;
|
||||
int _block_align;
|
||||
int _bytes_per_sample;
|
||||
bool _is_float;
|
||||
|
||||
std::streampos _data_start;
|
||||
std::streampos _data_pos;
|
||||
size_t _data_size;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
|
|
|
|||
|
|
@ -294,27 +294,61 @@ seek(double t) {
|
|||
t = std::max(t, 0.0);
|
||||
std::streampos pos = _data_start + (std::streampos) std::min((size_t) (t * _byte_rate), _data_size);
|
||||
|
||||
std::streambuf *buf = _stream->rdbuf();
|
||||
|
||||
if (_can_seek_fast) {
|
||||
_stream->seekg(pos);
|
||||
if (_stream->tellg() != pos) {
|
||||
if (buf->pubseekpos(pos, std::ios::in) != pos) {
|
||||
// Clearly, we can't seek fast. Fall back to the case below.
|
||||
_can_seek_fast = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_can_seek_fast) {
|
||||
std::streampos current = _stream->tellg();
|
||||
// Get the current position of the cursor in the file.
|
||||
std::streampos current = buf->pubseekoff(0, std::ios::cur, std::ios::in);
|
||||
|
||||
if (!_can_seek_fast) {
|
||||
if (pos > current) {
|
||||
// It is ahead of our current position. Skip ahead.
|
||||
_reader.skip_bytes(pos - current);
|
||||
_stream->ignore(pos - current);
|
||||
current = pos;
|
||||
|
||||
} else if (pos < current) {
|
||||
// We'll have to reopen the file. TODO
|
||||
// Can we seek to the beginning? Some streams, such as ZStream, let us
|
||||
// rewind the stream.
|
||||
if (buf->pubseekpos(0, std::ios::in) == (std::streampos)0) {
|
||||
if (pos > _data_start && movies_cat.is_info()) {
|
||||
Filename fn = get_source()->get_filename();
|
||||
movies_cat.info()
|
||||
<< "Unable to seek backwards in " << fn.get_basename()
|
||||
<< "; seeking to beginning and skipping " << pos << " bytes.\n";
|
||||
}
|
||||
_stream->ignore(pos);
|
||||
current = pos;
|
||||
} else {
|
||||
// No; close and reopen the file.
|
||||
Filename fn = get_source()->get_filename();
|
||||
movies_cat.warning()
|
||||
<< "Unable to seek backwards in " << fn.get_basename()
|
||||
<< "; reopening and skipping " << pos << " bytes.\n";
|
||||
|
||||
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
|
||||
std::istream *stream = vfs->open_read_file(get_source()->get_filename(), true);
|
||||
if (stream != nullptr) {
|
||||
vfs->close_read_file(_stream);
|
||||
stream->ignore(pos);
|
||||
_stream = stream;
|
||||
_reader = StreamReader(stream, false);
|
||||
current = pos;
|
||||
} else {
|
||||
movies_cat.error()
|
||||
<< "Unable to reopen " << fn << ".\n";
|
||||
_can_seek = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_data_pos = _stream->tellg() - _data_start;
|
||||
_data_pos = (size_t)current - _data_start;
|
||||
_last_seek = _data_pos / _byte_rate;
|
||||
_samples_read = 0;
|
||||
}
|
||||
|
|
@ -324,13 +358,13 @@ seek(double t) {
|
|||
* read. Your buffer must be equal in size to N * channels. Multiple-channel
|
||||
* audio will be interleaved.
|
||||
*/
|
||||
void WavAudioCursor::
|
||||
int WavAudioCursor::
|
||||
read_samples(int n, int16_t *data) {
|
||||
int desired = n * _audio_channels;
|
||||
int read_samples = std::min(desired, ((int) (_data_size - _data_pos)) / _bytes_per_sample);
|
||||
|
||||
if (read_samples <= 0) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (_format) {
|
||||
|
|
@ -421,8 +455,10 @@ read_samples(int n, int16_t *data) {
|
|||
// Fill the rest of the buffer with silence.
|
||||
if (read_samples < desired) {
|
||||
memset(data + read_samples, 0, (desired - read_samples) * 2);
|
||||
n = read_samples / _audio_channels;
|
||||
}
|
||||
|
||||
_data_pos = _stream->tellg() - _data_start;
|
||||
_samples_read += read_samples / _audio_channels;
|
||||
_samples_read += n;
|
||||
return n;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ PUBLISHED:
|
|||
virtual void seek(double offset);
|
||||
|
||||
public:
|
||||
virtual void read_samples(int n, int16_t *data);
|
||||
virtual int read_samples(int n, int16_t *data);
|
||||
|
||||
bool _is_valid;
|
||||
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ PUBLISHED:
|
|||
OdeJoint get_joint(int index) const;
|
||||
MAKE_SEQ(get_joints, get_num_joints, get_joint);
|
||||
EXTENSION(INLINE PyObject *get_converted_joint(int i) const);
|
||||
MAKE_SEQ_PROPERTY(joints, get_num_joints, get_converted_joint);
|
||||
|
||||
INLINE void enable();
|
||||
INLINE void disable();
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ PUBLISHED:
|
|||
INLINE void set_feedback(bool flag = true);
|
||||
INLINE OdeJointFeedback *get_feedback();
|
||||
|
||||
EXTENSION(void attach(const OdeBody *body1, const OdeBody *body2));
|
||||
EXTENSION(void attach(PyObject *body1, PyObject *body2));
|
||||
void attach_bodies(const OdeBody &body1, const OdeBody &body2);
|
||||
void attach_body(const OdeBody &body, int index);
|
||||
void detach();
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#include "odePlane2dJoint.h"
|
||||
|
||||
#ifndef CPPPARSER
|
||||
extern Dtool_PyTypedObject Dtool_OdeBody;
|
||||
extern Dtool_PyTypedObject Dtool_OdeJoint;
|
||||
extern Dtool_PyTypedObject Dtool_OdeBallJoint;
|
||||
extern Dtool_PyTypedObject Dtool_OdeHingeJoint;
|
||||
|
|
@ -48,7 +49,23 @@ extern Dtool_PyTypedObject Dtool_OdePlane2dJoint;
|
|||
* attached to the environment.
|
||||
*/
|
||||
void Extension<OdeJoint>::
|
||||
attach(const OdeBody *body1, const OdeBody *body2) {
|
||||
attach(PyObject *param1, PyObject *param2) {
|
||||
const OdeBody *body1 = nullptr;
|
||||
if (param1 != Py_None) {
|
||||
body1 = (const OdeBody *)DTOOL_Call_GetPointerThisClass(param1, &Dtool_OdeBody, 1, "OdeJoint.attach", true, true);
|
||||
if (body1 == nullptr) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const OdeBody *body2 = nullptr;
|
||||
if (param2 != Py_None) {
|
||||
body2 = (const OdeBody *)DTOOL_Call_GetPointerThisClass(param2, &Dtool_OdeBody, 2, "OdeJoint.attach", true, true);
|
||||
if (body2 == nullptr) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (body1 && body2) {
|
||||
_this->attach_bodies(*body1, *body2);
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
template<>
|
||||
class Extension<OdeJoint> : public ExtensionBase<OdeJoint> {
|
||||
public:
|
||||
void attach(const OdeBody *body1, const OdeBody *body2);
|
||||
void attach(PyObject *body1, PyObject *body2);
|
||||
|
||||
PyObject *convert() const;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -422,6 +422,57 @@ get_shader_input_ptr(const InternalName *id) const {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ShaderInput as a ShaderPtrData struct. Assertion fails if
|
||||
* there is none. or if it is not a PTA(double/float)
|
||||
*/
|
||||
bool ShaderAttrib::
|
||||
get_shader_input_ptr(const InternalName *id, Shader::ShaderPtrData &data) const {
|
||||
Inputs::const_iterator i = _inputs.find(id);
|
||||
if (i != _inputs.end()) {
|
||||
const ShaderInput &p = (*i).second;
|
||||
if (p.get_value_type() == ShaderInput::M_numeric ||
|
||||
p.get_value_type() == ShaderInput::M_vector) {
|
||||
|
||||
data = p.get_ptr();
|
||||
return (data._ptr != nullptr);
|
||||
}
|
||||
if (p.get_value_type() == ShaderInput::M_param) {
|
||||
// Temporary solution until the new param system
|
||||
TypedWritableReferenceCount *param = p.get_value();
|
||||
if (param != nullptr) {
|
||||
if (param->is_of_type(ParamVecBase4f::get_class_type())) {
|
||||
data._ptr = (void *)((const ParamVecBase4f *)param)->get_value().get_data();
|
||||
data._size = 4;
|
||||
data._type = Shader::SPT_float;
|
||||
return true;
|
||||
}
|
||||
else if (param->is_of_type(ParamVecBase4i::get_class_type())) {
|
||||
data._ptr = (void *)((const ParamVecBase4i *)param)->get_value().get_data();
|
||||
data._size = 4;
|
||||
data._type = Shader::SPT_int;
|
||||
return true;
|
||||
}
|
||||
else if (param->is_of_type(ParamVecBase4d::get_class_type())) {
|
||||
data._ptr = (void *)((const ParamVecBase4d *)param)->get_value().get_data();
|
||||
data._size = 4;
|
||||
data._type = Shader::SPT_float;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ostringstream strm;
|
||||
strm << "Shader input " << id->get_name() << " was given an incompatible parameter type.\n";
|
||||
nassert_raise(strm.str());
|
||||
return false;
|
||||
} else {
|
||||
ostringstream strm;
|
||||
strm << "Shader input " << id->get_name() << " is not present.\n";
|
||||
nassert_raise(strm.str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ShaderInput as a texture. Assertion fails if there is none, or
|
||||
* if it is not a texture.
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ PUBLISHED:
|
|||
LVecBase4 get_shader_input_vector(InternalName *id) const;
|
||||
Texture *get_shader_input_texture(const InternalName *id, SamplerState *sampler=nullptr) const;
|
||||
const Shader::ShaderPtrData *get_shader_input_ptr(const InternalName *id) const;
|
||||
bool get_shader_input_ptr(const InternalName *id, Shader::ShaderPtrData &data) const;
|
||||
const LMatrix4 &get_shader_input_matrix(const InternalName *id, LMatrix4 &matrix) const;
|
||||
ShaderBuffer *get_shader_input_buffer(const InternalName *id) const;
|
||||
|
||||
|
|
|
|||
|
|
@ -115,7 +115,11 @@ release(const MouseWatcherParameter ¶m, bool background) {
|
|||
if (has_click_button(param.get_button())) {
|
||||
_button_down = false;
|
||||
if (get_active()) {
|
||||
if (param.is_outside()) {
|
||||
// Note that a "click" may come from a keyboard button press. In that
|
||||
// case, instead of checking that the mouse cursor is still over the
|
||||
// button, we check whether the item has keyboard focus.
|
||||
if (param.is_outside() &&
|
||||
(MouseButton::is_mouse_button(param.get_button()) || !get_focus())) {
|
||||
set_state(S_ready);
|
||||
} else {
|
||||
set_state(S_rollover);
|
||||
|
|
|
|||
|
|
@ -70,6 +70,8 @@ acquire(Thread *current_thread) const {
|
|||
/**
|
||||
* Returns immediately, with a true value indicating the mutex has been
|
||||
* acquired, and false indicating it has not.
|
||||
*
|
||||
* @deprecated Python users should use acquire(False), C++ users try_lock()
|
||||
*/
|
||||
INLINE bool MutexDebug::
|
||||
try_acquire(Thread *current_thread) const {
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ acquire() const {
|
|||
/**
|
||||
* Returns immediately, with a true value indicating the mutex has been
|
||||
* acquired, and false indicating it has not.
|
||||
*
|
||||
* @deprecated Python users should use acquire(False), C++ users try_lock()
|
||||
*/
|
||||
INLINE bool MutexDirect::
|
||||
try_acquire() const {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,10 @@ PUBLISHED:
|
|||
|
||||
void operator = (const Mutex ©) = delete;
|
||||
|
||||
EXTENSION(bool acquire(bool blocking=true) const);
|
||||
EXTENSION(bool __enter__());
|
||||
EXTENSION(void __exit__(PyObject *, PyObject *, PyObject *));
|
||||
|
||||
public:
|
||||
// This is a global mutex set aside for the purpose of protecting Notify
|
||||
// messages from being interleaved between threads.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* 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 pmutex_ext.h
|
||||
* @author rdb
|
||||
* @date 2019-05-12
|
||||
*/
|
||||
|
||||
/**
|
||||
* Acquires the mutex.
|
||||
*/
|
||||
INLINE bool Extension<Mutex>::
|
||||
acquire(bool blocking) const {
|
||||
if (_this->try_lock()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!blocking) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Release the GIL while we are waiting for the lock.
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
PyThreadState *_save;
|
||||
Py_UNBLOCK_THREADS
|
||||
_this->lock();
|
||||
Py_BLOCK_THREADS
|
||||
#else
|
||||
_this->lock();
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires the mutex.
|
||||
*/
|
||||
INLINE bool Extension<Mutex>::
|
||||
__enter__() {
|
||||
return acquire(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the mutex.
|
||||
*/
|
||||
INLINE void Extension<Mutex>::
|
||||
__exit__(PyObject *, PyObject *, PyObject *) {
|
||||
_this->unlock();
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* 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 pmutex_ext.h
|
||||
* @author rdb
|
||||
* @date 2019-05-12
|
||||
*/
|
||||
|
||||
#ifndef PMUTEX_EXT_H
|
||||
#define PMUTEX_EXT_H
|
||||
|
||||
#include "dtoolbase.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
#include "extension.h"
|
||||
#include "pmutex.h"
|
||||
#include "py_panda.h"
|
||||
|
||||
/**
|
||||
* This class defines the extension methods for Mutex, which are called
|
||||
* instead of any C++ methods with the same prototype.
|
||||
*/
|
||||
template<>
|
||||
class Extension<Mutex> : public ExtensionBase<Mutex> {
|
||||
public:
|
||||
INLINE bool acquire(bool blocking) const;
|
||||
INLINE bool __enter__();
|
||||
INLINE void __exit__(PyObject *, PyObject *, PyObject *);
|
||||
};
|
||||
|
||||
#include "pmutex_ext.I"
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#endif // PMUTEX_EXT_H
|
||||
|
|
@ -42,6 +42,10 @@ PUBLISHED:
|
|||
~ReMutex() = default;
|
||||
|
||||
void operator = (const ReMutex ©) = delete;
|
||||
|
||||
EXTENSION(bool acquire(bool blocking=true) const);
|
||||
EXTENSION(bool __enter__());
|
||||
EXTENSION(void __exit__(PyObject *, PyObject *, PyObject *));
|
||||
};
|
||||
|
||||
#include "reMutex.I"
|
||||
|
|
|
|||
|
|
@ -105,6 +105,8 @@ acquire(Thread *current_thread) const {
|
|||
/**
|
||||
* Returns immediately, with a true value indicating the mutex has been
|
||||
* acquired, and false indicating it has not.
|
||||
*
|
||||
* @deprecated Python users should use acquire(False), C++ users try_lock()
|
||||
*/
|
||||
INLINE bool ReMutexDirect::
|
||||
try_acquire() const {
|
||||
|
|
@ -119,6 +121,8 @@ try_acquire() const {
|
|||
/**
|
||||
* Returns immediately, with a true value indicating the mutex has been
|
||||
* acquired, and false indicating it has not.
|
||||
*
|
||||
* @deprecated Python users should use acquire(False), C++ users try_lock()
|
||||
*/
|
||||
INLINE bool ReMutexDirect::
|
||||
try_acquire(Thread *current_thread) const {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* 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 pmutex_ext.h
|
||||
* @author rdb
|
||||
* @date 2019-05-12
|
||||
*/
|
||||
|
||||
/**
|
||||
* Acquires the mutex.
|
||||
*/
|
||||
INLINE bool Extension<ReMutex>::
|
||||
acquire(bool blocking) const {
|
||||
if (_this->try_lock()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!blocking) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Release the GIL while we are waiting for the lock.
|
||||
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
||||
PyThreadState *_save;
|
||||
Py_UNBLOCK_THREADS
|
||||
_this->lock();
|
||||
Py_BLOCK_THREADS
|
||||
#else
|
||||
_this->lock();
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires the mutex.
|
||||
*/
|
||||
INLINE bool Extension<ReMutex>::
|
||||
__enter__() {
|
||||
return acquire(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the mutex.
|
||||
*/
|
||||
INLINE void Extension<ReMutex>::
|
||||
__exit__(PyObject *, PyObject *, PyObject *) {
|
||||
_this->unlock();
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* 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 remutex_ext.h
|
||||
* @author rdb
|
||||
* @date 2019-05-12
|
||||
*/
|
||||
|
||||
#ifndef REMUTEX_EXT_H
|
||||
#define REMUTEX_EXT_H
|
||||
|
||||
#include "dtoolbase.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
#include "extension.h"
|
||||
#include "reMutex.h"
|
||||
#include "py_panda.h"
|
||||
|
||||
/**
|
||||
* This class defines the extension methods for ReMutex, which are called
|
||||
* instead of any C++ methods with the same prototype.
|
||||
*/
|
||||
template<>
|
||||
class Extension<ReMutex> : public ExtensionBase<ReMutex> {
|
||||
public:
|
||||
INLINE bool acquire(bool blocking) const;
|
||||
INLINE bool __enter__();
|
||||
INLINE void __exit__(PyObject *, PyObject *, PyObject *);
|
||||
};
|
||||
|
||||
#include "reMutex_ext.I"
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#endif // REMUTEX_EXT_H
|
||||
|
|
@ -262,8 +262,8 @@ compare_to(const SparseArray &other) const {
|
|||
return -1;
|
||||
}
|
||||
|
||||
--ai;
|
||||
--bi;
|
||||
++ai;
|
||||
++bi;
|
||||
}
|
||||
|
||||
if (ai != _subranges.rend()) {
|
||||
|
|
@ -440,9 +440,9 @@ do_remove_range(int begin, int end) {
|
|||
if (si == _subranges.end()) {
|
||||
if (!_subranges.empty()) {
|
||||
si = _subranges.begin() + _subranges.size() - 1;
|
||||
if ((*si)._end >= begin) {
|
||||
if ((*si)._end > begin) {
|
||||
// The new range shortens the last element of the array on the right.
|
||||
end = std::min(end, (*si)._begin);
|
||||
end = std::max(begin, (*si)._begin);
|
||||
(*si)._end = end;
|
||||
// It might also shorten it on the left; fall through.
|
||||
} else {
|
||||
|
|
@ -462,10 +462,10 @@ do_remove_range(int begin, int end) {
|
|||
if (si != _subranges.begin()) {
|
||||
Subranges::iterator si2 = si;
|
||||
--si2;
|
||||
if ((*si2)._end >= begin) {
|
||||
if ((*si2)._end > begin) {
|
||||
// The new range shortens an element within the array on the right
|
||||
// (but does not intersect the next element).
|
||||
end = std::min(end, (*si2)._begin);
|
||||
end = std::max(begin, (*si2)._begin);
|
||||
(*si2)._end = end;
|
||||
// It might also shorten it on the left; fall through.
|
||||
si = si2;
|
||||
|
|
@ -488,7 +488,7 @@ do_remove_range(int begin, int end) {
|
|||
}
|
||||
|
||||
// Check if the new range removes any elements to the left.
|
||||
while (begin <= (*si)._begin) {
|
||||
while (begin <= (*si)._begin || (*si)._begin >= (*si)._end) {
|
||||
if (si == _subranges.begin()) {
|
||||
_subranges.erase(si);
|
||||
return;
|
||||
|
|
@ -500,6 +500,7 @@ do_remove_range(int begin, int end) {
|
|||
}
|
||||
|
||||
(*si)._end = std::min((*si)._end, begin);
|
||||
nassertv((*si)._end > (*si)._begin);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -609,9 +609,20 @@ make_context(HDC hdc) {
|
|||
attrib_list[n++] = gl_version[1];
|
||||
}
|
||||
}
|
||||
int flags = 0;
|
||||
if (gl_debug) {
|
||||
flags |= WGL_CONTEXT_DEBUG_BIT_ARB;
|
||||
}
|
||||
if (gl_forward_compatible) {
|
||||
flags |= WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB;
|
||||
if (gl_version.get_num_words() == 0 || gl_version[0] < 2) {
|
||||
wgldisplay_cat.error()
|
||||
<< "gl-forward-compatible requires gl-version >= 3 0\n";
|
||||
}
|
||||
}
|
||||
if (flags != 0) {
|
||||
attrib_list[n++] = WGL_CONTEXT_FLAGS_ARB;
|
||||
attrib_list[n++] = WGL_CONTEXT_DEBUG_BIT_ARB;
|
||||
attrib_list[n++] = flags;
|
||||
}
|
||||
#ifndef SUPPORT_FIXED_FUNCTION
|
||||
attrib_list[n++] = WGL_CONTEXT_PROFILE_MASK_ARB;
|
||||
|
|
|
|||
|
|
@ -86,6 +86,11 @@ ConfigVariableBool swapbuffer_framelock
|
|||
("swapbuffer-framelock", false,
|
||||
PRC_DESC("Set this true to enable HW swapbuffer frame-lock on 3dlabs cards"));
|
||||
|
||||
ConfigVariableBool paste_emit_keystrokes
|
||||
("paste-emit-keystrokes", true,
|
||||
PRC_DESC("Handle paste events (Ctrl-V) as separate keystroke events for each "
|
||||
"pasted character."));
|
||||
|
||||
/**
|
||||
* Initializes the library. This must be called at least once before any of
|
||||
* the functions or classes in this library can be used. Normally it will be
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ extern ConfigVariableBool ime_hide;
|
|||
extern ConfigVariableBool request_dxdisplay_information;
|
||||
extern ConfigVariableBool dpi_aware;
|
||||
extern ConfigVariableBool dpi_window_resize;
|
||||
extern ConfigVariableBool paste_emit_keystrokes;
|
||||
|
||||
extern EXPCL_PANDAWIN ConfigVariableBool swapbuffer_framelock;
|
||||
|
||||
|
|
|
|||
|
|
@ -1927,7 +1927,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
|
|||
// Handle Cntrl-V paste from clipboard. Is there a better way to detect
|
||||
// this hotkey?
|
||||
if ((wparam=='V') && (GetKeyState(VK_CONTROL) < 0) &&
|
||||
!_input_devices.empty()) {
|
||||
!_input_devices.empty() && paste_emit_keystrokes) {
|
||||
HGLOBAL hglb;
|
||||
char *lptstr;
|
||||
|
||||
|
|
|
|||
|
|
@ -589,7 +589,13 @@ set_properties_now(WindowProperties &properties) {
|
|||
// OK, first figure out which CRTC the window is on. It may be on more
|
||||
// than one, actually, so grab a point in the center in order to figure
|
||||
// out which one it's more-or-less mostly on.
|
||||
LPoint2i center = _properties.get_origin() + _properties.get_size() / 2;
|
||||
LPoint2i center(0, 0);
|
||||
if (_properties.has_origin()) {
|
||||
center = _properties.get_origin();
|
||||
if (_properties.has_size()) {
|
||||
center += _properties.get_size() / 2;
|
||||
}
|
||||
}
|
||||
int x, y, width, height;
|
||||
x11_pipe->find_fullscreen_crtc(center, x, y, width, height);
|
||||
|
||||
|
|
@ -628,7 +634,7 @@ set_properties_now(WindowProperties &properties) {
|
|||
// We may need to change the screen resolution. The code below is
|
||||
// suboptimal; in the future, we probably want to only touch the CRTC
|
||||
// that the window is on.
|
||||
XRRScreenConfiguration *conf = _XRRGetScreenInfo(_display, _xwindow);
|
||||
XRRScreenConfiguration *conf = _XRRGetScreenInfo(_display, _xwindow ? _xwindow : x11_pipe->get_root());
|
||||
SizeID old_size_id = x11_pipe->_XRRConfigCurrentConfiguration(conf, &_orig_rotation);
|
||||
SizeID new_size_id = (SizeID) -1;
|
||||
int num_sizes = 0;
|
||||
|
|
@ -1010,34 +1016,6 @@ open_window() {
|
|||
// Make sure we are not making X11 calls from other threads.
|
||||
LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
|
||||
|
||||
if (_properties.get_fullscreen() && x11_pipe->_have_xrandr) {
|
||||
XRRScreenConfiguration* conf = _XRRGetScreenInfo(_display, x11_pipe->get_root());
|
||||
if (_orig_size_id == (SizeID) -1) {
|
||||
_orig_size_id = x11_pipe->_XRRConfigCurrentConfiguration(conf, &_orig_rotation);
|
||||
}
|
||||
int num_sizes, new_size_id = -1;
|
||||
XRRScreenSize *xrrs;
|
||||
xrrs = x11_pipe->_XRRSizes(_display, 0, &num_sizes);
|
||||
for (int i = 0; i < num_sizes; ++i) {
|
||||
if (xrrs[i].width == _properties.get_x_size() &&
|
||||
xrrs[i].height == _properties.get_y_size()) {
|
||||
new_size_id = i;
|
||||
}
|
||||
}
|
||||
if (new_size_id == -1) {
|
||||
x11display_cat.error()
|
||||
<< "Videocard has no supported display resolutions at specified res ("
|
||||
<< _properties.get_x_size() << " x " << _properties.get_y_size() <<")\n";
|
||||
_orig_size_id = -1;
|
||||
return false;
|
||||
}
|
||||
if (new_size_id != _orig_size_id) {
|
||||
_XRRSetScreenConfig(_display, conf, x11_pipe->get_root(), new_size_id, _orig_rotation, CurrentTime);
|
||||
} else {
|
||||
_orig_size_id = -1;
|
||||
}
|
||||
}
|
||||
|
||||
X11_Window parent_window = x11_pipe->get_root();
|
||||
WindowHandle *window_handle = _properties.get_parent_window();
|
||||
if (window_handle != nullptr) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
import pytest
|
||||
# Skip these tests if we can't import bullet.
|
||||
bullet = pytest.importorskip("panda3d.bullet")
|
||||
|
||||
from panda3d.bullet import BulletWorld, BulletRigidBodyNode, ZUp
|
||||
from panda3d.bullet import BulletHeightfieldShape, BulletSphereShape
|
||||
from panda3d.core import NodePath, PNMImage
|
||||
|
||||
|
||||
def make_node(name, BulletShape, *args):
|
||||
# Returns a BulletRigidBodyNode for the given shape
|
||||
shape = BulletShape(*args)
|
||||
node = BulletRigidBodyNode(name)
|
||||
node.add_shape(shape)
|
||||
return node
|
||||
|
||||
|
||||
def test_sphere_into_heightfield():
|
||||
root = NodePath("root")
|
||||
world = BulletWorld()
|
||||
# Create PNMImage to construct Heightfield with
|
||||
img = PNMImage(10, 10, 1)
|
||||
img.fill_val(255)
|
||||
# Make our nodes
|
||||
heightfield = make_node("Heightfield", BulletHeightfieldShape, img, 1, ZUp)
|
||||
sphere = make_node("Sphere", BulletSphereShape, 1)
|
||||
# Attach to world
|
||||
np1 = root.attach_new_node(sphere)
|
||||
np1.set_pos(0, 0, 1)
|
||||
world.attach(sphere)
|
||||
|
||||
np2 = root.attach_new_node(heightfield)
|
||||
np2.set_pos(0, 0, 0)
|
||||
world.attach(heightfield)
|
||||
|
||||
assert world.get_num_rigid_bodies() == 2
|
||||
test = world.contact_test_pair(sphere, heightfield)
|
||||
assert test.get_num_contacts() > 0
|
||||
assert test.get_contact(0).get_node0() == sphere
|
||||
assert test.get_contact(0).get_node1() == heightfield
|
||||
|
||||
# Increment sphere's Z coordinate, no longer colliding
|
||||
np1.set_pos(0, 0, 2)
|
||||
test = world.contact_test_pair(sphere, heightfield)
|
||||
assert test.get_num_contacts() == 0
|
||||
|
|
@ -294,6 +294,36 @@ def test_glsl_pta_mat4(gsg):
|
|||
run_glsl_test(gsg, code, preamble, {'pta': pta})
|
||||
|
||||
|
||||
def test_glsl_param_vec4(gsg):
|
||||
param = core.ParamVecBase4((0, 1, 2, 3))
|
||||
|
||||
preamble = """
|
||||
uniform vec4 param;
|
||||
"""
|
||||
code = """
|
||||
assert(param.x == 0.0);
|
||||
assert(param.y == 1.0);
|
||||
assert(param.z == 2.0);
|
||||
assert(param.w == 3.0);
|
||||
"""
|
||||
run_glsl_test(gsg, code, preamble, {'param': param})
|
||||
|
||||
|
||||
def test_glsl_param_ivec4(gsg):
|
||||
param = core.ParamVecBase4i((0, 1, 2, 3))
|
||||
|
||||
preamble = """
|
||||
uniform ivec4 param;
|
||||
"""
|
||||
code = """
|
||||
assert(param.x == 0);
|
||||
assert(param.y == 1);
|
||||
assert(param.z == 2);
|
||||
assert(param.w == 3);
|
||||
"""
|
||||
run_glsl_test(gsg, code, preamble, {'param': param})
|
||||
|
||||
|
||||
def test_glsl_write_extract_image_buffer(gsg):
|
||||
# Tests that we can write to a buffer texture on the GPU, and then extract
|
||||
# the data on the CPU. We test two textures since there was in the past a
|
||||
|
|
|
|||
|
|
@ -0,0 +1,128 @@
|
|||
from panda3d.core import StringStream
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
ISTREAM_DATA = b'abcdefghijklmnopqrstuvwxyz' * 500
|
||||
|
||||
@pytest.fixture
|
||||
def istream():
|
||||
return StringStream(ISTREAM_DATA)
|
||||
|
||||
|
||||
def test_istream_readall(istream):
|
||||
assert istream.readall() == ISTREAM_DATA
|
||||
assert istream.readall() == b''
|
||||
assert istream.readall() == b''
|
||||
assert istream.tellg() == len(ISTREAM_DATA)
|
||||
|
||||
|
||||
def test_istream_read(istream):
|
||||
assert istream.read() == ISTREAM_DATA
|
||||
assert istream.read() == b''
|
||||
assert istream.read() == b''
|
||||
assert istream.tellg() == len(ISTREAM_DATA)
|
||||
|
||||
|
||||
def test_istream_read_size(istream):
|
||||
assert istream.read(100) == ISTREAM_DATA[:100]
|
||||
assert istream.read(5000) == ISTREAM_DATA[100:5100]
|
||||
assert istream.read(5000) == ISTREAM_DATA[5100:10100]
|
||||
assert istream.read(5000) == ISTREAM_DATA[10100:15100]
|
||||
assert istream.read() == b''
|
||||
assert istream.tellg() == len(ISTREAM_DATA)
|
||||
|
||||
|
||||
def test_istream_read1(istream):
|
||||
accumulated = b''
|
||||
data = istream.read1()
|
||||
while data:
|
||||
accumulated += data
|
||||
data = istream.read1()
|
||||
|
||||
assert accumulated == ISTREAM_DATA
|
||||
assert istream.tellg() == len(ISTREAM_DATA)
|
||||
|
||||
|
||||
def test_istream_read1_size(istream):
|
||||
accumulated = b''
|
||||
data = istream.read1(4000)
|
||||
while data:
|
||||
accumulated += data
|
||||
data = istream.read1(4000)
|
||||
|
||||
assert accumulated == ISTREAM_DATA
|
||||
assert istream.tellg() == len(ISTREAM_DATA)
|
||||
|
||||
|
||||
def test_istream_readinto(istream):
|
||||
ba = bytearray()
|
||||
assert istream.readinto(ba) == 0
|
||||
assert istream.tellg() == 0
|
||||
|
||||
ba = bytearray(10)
|
||||
assert istream.readinto(ba) == 10
|
||||
assert ba == ISTREAM_DATA[:10]
|
||||
assert istream.tellg() == 10
|
||||
|
||||
ba = bytearray(len(ISTREAM_DATA))
|
||||
assert istream.readinto(ba) == len(ISTREAM_DATA) - 10
|
||||
assert ba[:len(ISTREAM_DATA)-10] == ISTREAM_DATA[10:]
|
||||
assert istream.tellg() == len(ISTREAM_DATA)
|
||||
|
||||
|
||||
def test_istream_readline():
|
||||
# Empty stream
|
||||
stream = StringStream(b'')
|
||||
assert stream.readline() == b''
|
||||
assert stream.readline() == b''
|
||||
|
||||
# Single line without newline
|
||||
stream = StringStream(b'A')
|
||||
assert stream.readline() == b'A'
|
||||
assert stream.readline() == b''
|
||||
|
||||
# Single newline
|
||||
stream = StringStream(b'\n')
|
||||
assert stream.readline() == b'\n'
|
||||
assert stream.readline() == b''
|
||||
|
||||
# Line with text followed by empty line
|
||||
stream = StringStream(b'A\n\n')
|
||||
assert stream.readline() == b'A\n'
|
||||
assert stream.readline() == b'\n'
|
||||
assert stream.readline() == b''
|
||||
|
||||
# Preserve null byte
|
||||
stream = StringStream(b'\x00\x00')
|
||||
assert stream.readline() == b'\x00\x00'
|
||||
|
||||
|
||||
def test_istream_readlines():
|
||||
istream = StringStream(b'a')
|
||||
assert istream.readlines() == [b'a']
|
||||
assert istream.readlines() == []
|
||||
|
||||
istream = StringStream(b'a\nb\nc\n')
|
||||
assert istream.readlines() == [b'a\n', b'b\n', b'c\n']
|
||||
|
||||
istream = StringStream(b'\na\nb\nc')
|
||||
assert istream.readlines() == [b'\n', b'a\n', b'b\n', b'c']
|
||||
|
||||
istream = StringStream(b'\n\n\n')
|
||||
assert istream.readlines() == [b'\n', b'\n', b'\n']
|
||||
|
||||
|
||||
def test_istream_iter():
|
||||
istream = StringStream(b'a')
|
||||
assert tuple(istream) == (b'a',)
|
||||
assert tuple(istream) == ()
|
||||
|
||||
istream = StringStream(b'a\nb\nc\n')
|
||||
assert tuple(istream) == (b'a\n', b'b\n', b'c\n')
|
||||
|
||||
istream = StringStream(b'\na\nb\nc')
|
||||
assert tuple(istream) == (b'\n', b'a\n', b'b\n', b'c')
|
||||
|
||||
istream = StringStream(b'\n\n\n')
|
||||
assert tuple(istream) == (b'\n', b'\n', b'\n')
|
||||
|
|
@ -87,3 +87,39 @@ def test_mat4_invert_correct(type):
|
|||
|
||||
assert (mat * inv).is_identity()
|
||||
assert (inv * mat).is_identity()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("type", (core.LMatrix4d, core.LMatrix4f))
|
||||
def test_mat4_rows(type):
|
||||
mat = type((1, 2, 3, 4,
|
||||
5, 6, 7, 8,
|
||||
9, 10, 11, 12,
|
||||
13, 14, 15, 16))
|
||||
|
||||
assert mat.rows[0] == (1, 2, 3, 4)
|
||||
assert mat.rows[1] == (5, 6, 7, 8)
|
||||
assert mat.rows[2] == (9, 10, 11, 12)
|
||||
assert mat.rows[3] == (13, 14, 15, 16)
|
||||
|
||||
assert mat.get_row3(0) == (1, 2, 3)
|
||||
assert mat.get_row3(1) == (5, 6, 7)
|
||||
assert mat.get_row3(2) == (9, 10, 11)
|
||||
assert mat.get_row3(3) == (13, 14, 15)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("type", (core.LMatrix4d, core.LMatrix4f))
|
||||
def test_mat4_cols(type):
|
||||
mat = type((1, 5, 9, 13,
|
||||
2, 6, 10, 14,
|
||||
3, 7, 11, 15,
|
||||
4, 8, 12, 16))
|
||||
|
||||
assert mat.cols[0] == (1, 2, 3, 4)
|
||||
assert mat.cols[1] == (5, 6, 7, 8)
|
||||
assert mat.cols[2] == (9, 10, 11, 12)
|
||||
assert mat.cols[3] == (13, 14, 15, 16)
|
||||
|
||||
assert mat.get_col3(0) == (1, 2, 3)
|
||||
assert mat.get_col3(1) == (5, 6, 7)
|
||||
assert mat.get_col3(2) == (9, 10, 11)
|
||||
assert mat.get_col3(3) == (13, 14, 15)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue