Compare commits
3 Commits
5ba4a35f27
...
728d9aeddc
Author | SHA1 | Date | |
---|---|---|---|
728d9aeddc | |||
79cdba7b9c | |||
1cfd6cc066 |
@ -266,30 +266,29 @@ class DWRITE_CLUSTER_METRICS(ctypes.Structure):
|
||||
class IDWriteFontFileStream(com.IUnknown):
|
||||
_methods_ = [
|
||||
('ReadFileFragment',
|
||||
com.STDMETHOD(c_void_p, POINTER(c_void_p), UINT64, UINT64, POINTER(c_void_p))),
|
||||
com.STDMETHOD(POINTER(c_void_p), UINT64, UINT64, POINTER(c_void_p))),
|
||||
('ReleaseFileFragment',
|
||||
com.STDMETHOD(c_void_p, c_void_p)),
|
||||
com.STDMETHOD(c_void_p)),
|
||||
('GetFileSize',
|
||||
com.STDMETHOD(c_void_p, POINTER(UINT64))),
|
||||
com.STDMETHOD(POINTER(UINT64))),
|
||||
('GetLastWriteTime',
|
||||
com.STDMETHOD(c_void_p, POINTER(UINT64))),
|
||||
com.STDMETHOD(POINTER(UINT64))),
|
||||
]
|
||||
|
||||
|
||||
class IDWriteFontFileLoader_LI(com.IUnknown): # Local implementation use only.
|
||||
_methods_ = [
|
||||
('CreateStreamFromKey',
|
||||
com.STDMETHOD(c_void_p, c_void_p, UINT32, POINTER(POINTER(IDWriteFontFileStream))))
|
||||
com.STDMETHOD(c_void_p, UINT32, POINTER(POINTER(IDWriteFontFileStream))))
|
||||
]
|
||||
|
||||
|
||||
class IDWriteFontFileLoader(com.pIUnknown):
|
||||
_methods_ = [
|
||||
('CreateStreamFromKey',
|
||||
com.STDMETHOD(c_void_p, c_void_p, UINT32, POINTER(POINTER(IDWriteFontFileStream))))
|
||||
com.STDMETHOD(c_void_p, UINT32, POINTER(POINTER(IDWriteFontFileStream))))
|
||||
]
|
||||
|
||||
|
||||
class IDWriteLocalFontFileLoader(IDWriteFontFileLoader, com.pIUnknown):
|
||||
_methods_ = [
|
||||
('GetFilePathLengthFromKey',
|
||||
@ -452,13 +451,13 @@ DWRITE_READING_DIRECTION_LEFT_TO_RIGHT = 0
|
||||
class IDWriteTextAnalysisSource(com.IUnknown):
|
||||
_methods_ = [
|
||||
('GetTextAtPosition',
|
||||
com.METHOD(HRESULT, c_void_p, UINT32, POINTER(c_wchar_p), POINTER(UINT32))),
|
||||
com.STDMETHOD(UINT32, POINTER(c_wchar_p), POINTER(UINT32))),
|
||||
('GetTextBeforePosition',
|
||||
com.STDMETHOD(UINT32, c_wchar_p, POINTER(UINT32))),
|
||||
com.STDMETHOD(UINT32, POINTER(c_wchar_p), POINTER(UINT32))),
|
||||
('GetParagraphReadingDirection',
|
||||
com.METHOD(DWRITE_READING_DIRECTION)),
|
||||
('GetLocaleName',
|
||||
com.STDMETHOD(c_void_p, UINT32, POINTER(UINT32), POINTER(c_wchar_p))),
|
||||
com.STDMETHOD(UINT32, POINTER(UINT32), POINTER(c_wchar_p))),
|
||||
('GetNumberSubstitution',
|
||||
com.STDMETHOD(UINT32, POINTER(UINT32), c_void_p)),
|
||||
]
|
||||
@ -467,7 +466,7 @@ class IDWriteTextAnalysisSource(com.IUnknown):
|
||||
class IDWriteTextAnalysisSink(com.IUnknown):
|
||||
_methods_ = [
|
||||
('SetScriptAnalysis',
|
||||
com.STDMETHOD(c_void_p, UINT32, UINT32, POINTER(DWRITE_SCRIPT_ANALYSIS))),
|
||||
com.STDMETHOD(UINT32, UINT32, POINTER(DWRITE_SCRIPT_ANALYSIS))),
|
||||
('SetLineBreakpoints',
|
||||
com.STDMETHOD(UINT32, UINT32, c_void_p)),
|
||||
('SetBidiLevel',
|
||||
@ -524,7 +523,7 @@ class TextAnalysis(com.COMObject):
|
||||
|
||||
analyzer.AnalyzeScript(self, 0, text_length, self)
|
||||
|
||||
def SetScriptAnalysis(self, this, textPosition, textLength, scriptAnalysis):
|
||||
def SetScriptAnalysis(self, textPosition, textLength, scriptAnalysis):
|
||||
# textPosition - The index of the first character in the string that the result applies to
|
||||
# textLength - How many characters of the string from the index that the result applies to
|
||||
# scriptAnalysis - The analysis information for all glyphs starting at position for length.
|
||||
@ -542,10 +541,10 @@ class TextAnalysis(com.COMObject):
|
||||
return 0
|
||||
# return 0x80004001
|
||||
|
||||
def GetTextBeforePosition(self, this, textPosition, textString, textLength):
|
||||
def GetTextBeforePosition(self, textPosition, textString, textLength):
|
||||
raise Exception("Currently not implemented.")
|
||||
|
||||
def GetTextAtPosition(self, this, textPosition, textString, textLength):
|
||||
def GetTextAtPosition(self, textPosition, textString, textLength):
|
||||
# This method will retrieve a substring of the text in this layout
|
||||
# to be used in an analysis step.
|
||||
# Arguments:
|
||||
@ -568,7 +567,7 @@ class TextAnalysis(com.COMObject):
|
||||
def GetParagraphReadingDirection(self):
|
||||
return 0
|
||||
|
||||
def GetLocaleName(self, this, textPosition, textLength, localeName):
|
||||
def GetLocaleName(self, textPosition, textLength, localeName):
|
||||
self.__local_name = c_wchar_p("") # TODO: Add more locales.
|
||||
localeName[0] = self.__local_name
|
||||
textLength[0] = self._textlength - textPosition
|
||||
@ -954,16 +953,16 @@ class IDWriteTextLayout1(IDWriteTextLayout, IDWriteTextFormat, com.pIUnknown):
|
||||
class IDWriteFontFileEnumerator(com.IUnknown):
|
||||
_methods_ = [
|
||||
('MoveNext',
|
||||
com.STDMETHOD(c_void_p, POINTER(BOOL))),
|
||||
com.STDMETHOD(POINTER(BOOL))),
|
||||
('GetCurrentFontFile',
|
||||
com.STDMETHOD(c_void_p, c_void_p)),
|
||||
com.STDMETHOD(c_void_p)),
|
||||
]
|
||||
|
||||
|
||||
class IDWriteFontCollectionLoader(com.IUnknown):
|
||||
_methods_ = [
|
||||
('CreateEnumeratorFromKey',
|
||||
com.STDMETHOD(c_void_p, c_void_p, c_void_p, UINT32, POINTER(POINTER(IDWriteFontFileEnumerator)))),
|
||||
com.STDMETHOD(c_void_p, c_void_p, UINT32, POINTER(POINTER(IDWriteFontFileEnumerator)))),
|
||||
]
|
||||
|
||||
|
||||
@ -971,20 +970,12 @@ class MyFontFileStream(com.COMObject):
|
||||
_interfaces_ = [IDWriteFontFileStream]
|
||||
|
||||
def __init__(self, data):
|
||||
super().__init__()
|
||||
self._data = data
|
||||
self._size = len(data)
|
||||
self._ptrs = []
|
||||
|
||||
def AddRef(self, this):
|
||||
return 1
|
||||
|
||||
def Release(self, this):
|
||||
return 1
|
||||
|
||||
def QueryInterface(self, this, refiid, tester):
|
||||
return 0
|
||||
|
||||
def ReadFileFragment(self, this, fragmentStart, fileOffset, fragmentSize, fragmentContext):
|
||||
def ReadFileFragment(self, fragmentStart, fileOffset, fragmentSize, fragmentContext):
|
||||
if fileOffset + fragmentSize > self._size:
|
||||
return 0x80004005 # E_FAIL
|
||||
|
||||
@ -997,14 +988,14 @@ class MyFontFileStream(com.COMObject):
|
||||
fragmentContext[0] = None
|
||||
return 0
|
||||
|
||||
def ReleaseFileFragment(self, this, fragmentContext):
|
||||
def ReleaseFileFragment(self, fragmentContext):
|
||||
return 0
|
||||
|
||||
def GetFileSize(self, this, fileSize):
|
||||
def GetFileSize(self, fileSize):
|
||||
fileSize[0] = self._size
|
||||
return 0
|
||||
|
||||
def GetLastWriteTime(self, this, lastWriteTime):
|
||||
def GetLastWriteTime(self, lastWriteTime):
|
||||
return 0x80004001 # E_NOTIMPL
|
||||
|
||||
|
||||
@ -1012,21 +1003,13 @@ class LegacyFontFileLoader(com.COMObject):
|
||||
_interfaces_ = [IDWriteFontFileLoader_LI]
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._streams = {}
|
||||
|
||||
def QueryInterface(self, this, refiid, tester):
|
||||
return 0
|
||||
|
||||
def AddRef(self, this):
|
||||
return 1
|
||||
|
||||
def Release(self, this):
|
||||
return 1
|
||||
|
||||
def CreateStreamFromKey(self, this, fontfileReferenceKey, fontFileReferenceKeySize, fontFileStream):
|
||||
def CreateStreamFromKey(self, fontfileReferenceKey, fontFileReferenceKeySize, fontFileStream):
|
||||
convert_index = cast(fontfileReferenceKey, POINTER(c_uint32))
|
||||
|
||||
self._ptr = ctypes.cast(self._streams[convert_index.contents.value]._pointers[IDWriteFontFileStream],
|
||||
self._ptr = ctypes.cast(self._streams[convert_index.contents.value].as_interface(IDWriteFontFileStream),
|
||||
POINTER(IDWriteFontFileStream))
|
||||
fontFileStream[0] = self._ptr
|
||||
return 0
|
||||
@ -1039,6 +1022,7 @@ class MyEnumerator(com.COMObject):
|
||||
_interfaces_ = [IDWriteFontFileEnumerator]
|
||||
|
||||
def __init__(self, factory, loader):
|
||||
super().__init__()
|
||||
self.factory = cast(factory, IDWriteFactory)
|
||||
self.key = "pyglet_dwrite"
|
||||
self.size = len(self.key)
|
||||
@ -1057,7 +1041,7 @@ class MyEnumerator(com.COMObject):
|
||||
def AddFontData(self, fonts):
|
||||
self._font_data = fonts
|
||||
|
||||
def MoveNext(self, this, hasCurrentFile):
|
||||
def MoveNext(self, hasCurrentFile):
|
||||
|
||||
self.current_index += 1
|
||||
if self.current_index != len(self._font_data):
|
||||
@ -1087,7 +1071,7 @@ class MyEnumerator(com.COMObject):
|
||||
|
||||
pass
|
||||
|
||||
def GetCurrentFontFile(self, this, fontFile):
|
||||
def GetCurrentFontFile(self, fontFile):
|
||||
fontFile = cast(fontFile, POINTER(IDWriteFontFile))
|
||||
fontFile[0] = self._font_files[self.current_index]
|
||||
return 0
|
||||
@ -1097,24 +1081,14 @@ class LegacyCollectionLoader(com.COMObject):
|
||||
_interfaces_ = [IDWriteFontCollectionLoader]
|
||||
|
||||
def __init__(self, factory, loader):
|
||||
super().__init__()
|
||||
self._enumerator = MyEnumerator(factory, loader)
|
||||
|
||||
def AddFontData(self, fonts):
|
||||
self._enumerator.AddFontData(fonts)
|
||||
|
||||
def AddRef(self, this):
|
||||
self._i = 1
|
||||
return 1
|
||||
|
||||
def Release(self, this):
|
||||
self._i = 0
|
||||
return 1
|
||||
|
||||
def QueryInterface(self, this, refiid, tester):
|
||||
return 0
|
||||
|
||||
def CreateEnumeratorFromKey(self, this, factory, key, key_size, enumerator):
|
||||
self._ptr = ctypes.cast(self._enumerator._pointers[IDWriteFontFileEnumerator],
|
||||
def CreateEnumeratorFromKey(self, factory, key, key_size, enumerator):
|
||||
self._ptr = ctypes.cast(self._enumerator.as_interface(IDWriteFontFileEnumerator),
|
||||
POINTER(IDWriteFontFileEnumerator))
|
||||
|
||||
enumerator[0] = self._ptr
|
||||
@ -2418,7 +2392,7 @@ class Win32DirectWriteFont(base.Font):
|
||||
|
||||
# Note: RegisterFontLoader takes a pointer. However, for legacy we implement our own callback interface.
|
||||
# Therefore we need to pass to the actual pointer directly.
|
||||
cls._write_factory.RegisterFontFileLoader(cls._font_loader.pointers[IDWriteFontFileLoader_LI])
|
||||
cls._write_factory.RegisterFontFileLoader(cls._font_loader.as_interface(IDWriteFontFileLoader_LI))
|
||||
|
||||
cls._font_collection_loader = LegacyCollectionLoader(cls._write_factory, cls._font_loader)
|
||||
cls._write_factory.RegisterFontCollectionLoader(cls._font_collection_loader)
|
||||
@ -2472,7 +2446,7 @@ class Win32DirectWriteFont(base.Font):
|
||||
cls._font_collection_loader = LegacyCollectionLoader(cls._write_factory, cls._font_loader)
|
||||
|
||||
cls._write_factory.RegisterFontCollectionLoader(cls._font_collection_loader)
|
||||
cls._write_factory.RegisterFontFileLoader(cls._font_loader.pointers[IDWriteFontFileLoader_LI])
|
||||
cls._write_factory.RegisterFontFileLoader(cls._font_loader.as_interface(IDWriteFontFileLoader_LI))
|
||||
|
||||
cls._font_collection_loader.AddFontData(cls._font_cache)
|
||||
|
||||
|
@ -170,14 +170,14 @@ class Attribute:
|
||||
self.name = name
|
||||
self.location = location
|
||||
self.count = count
|
||||
|
||||
self.gl_type = gl_type
|
||||
self.c_type = _c_types[gl_type]
|
||||
self.normalize = normalize
|
||||
|
||||
self.align = sizeof(self.c_type)
|
||||
self.size = count * self.align
|
||||
self.stride = self.size
|
||||
self.c_type = _c_types[gl_type]
|
||||
|
||||
self.element_size = sizeof(self.c_type)
|
||||
self.byte_size = count * self.element_size
|
||||
self.stride = self.byte_size
|
||||
|
||||
def enable(self):
|
||||
"""Enable the attribute."""
|
||||
@ -216,15 +216,11 @@ class Attribute:
|
||||
|
||||
:rtype: `AbstractBufferRegion`
|
||||
"""
|
||||
byte_start = self.stride * start
|
||||
byte_size = self.stride * count
|
||||
array_count = self.count * count
|
||||
ptr_type = POINTER(self.c_type * array_count)
|
||||
return buffer.get_region(byte_start, byte_size, ptr_type)
|
||||
return buffer.get_region(start, count)
|
||||
|
||||
def set_region(self, buffer, start, count, data):
|
||||
"""Set the data over a region of the buffer.
|
||||
|
||||
"""Set the data over a region of the buffer.
|
||||
:Parameters:
|
||||
`buffer` : AbstractMappable`
|
||||
The buffer to modify.
|
||||
@ -234,9 +230,10 @@ class Attribute:
|
||||
Number of vertices to set.
|
||||
`data` : A sequence of data components.
|
||||
"""
|
||||
byte_start = self.stride * start
|
||||
byte_size = self.stride * count
|
||||
array_count = self.count * count
|
||||
byte_start = self.stride * start # byte offset
|
||||
byte_size = self.stride * count # number of bytes
|
||||
array_count = self.count * count # umber of values
|
||||
|
||||
data = (self.c_type * array_count)(*data)
|
||||
buffer.set_data_region(data, byte_start, byte_size)
|
||||
|
||||
|
@ -11,6 +11,8 @@ the buffer.
|
||||
import sys
|
||||
import ctypes
|
||||
|
||||
from functools import lru_cache
|
||||
|
||||
import pyglet
|
||||
from pyglet.gl import *
|
||||
|
||||
@ -98,36 +100,6 @@ class AbstractBuffer:
|
||||
raise NotImplementedError('abstract')
|
||||
|
||||
|
||||
class AbstractMappable:
|
||||
|
||||
def get_region(self, start, size, ptr_type):
|
||||
"""Map a region of the buffer into a ctypes array of the desired
|
||||
type. This region does not need to be unmapped, but will become
|
||||
invalid if the buffer is resized.
|
||||
|
||||
Note that although a pointer type is required, an array is mapped.
|
||||
For example::
|
||||
|
||||
get_region(0, ctypes.sizeof(c_int) * 20, ctypes.POINTER(c_int * 20))
|
||||
|
||||
will map bytes 0 to 80 of the buffer to an array of 20 ints.
|
||||
|
||||
Changes to the array may not be recognised until the region's
|
||||
:py:meth:`AbstractBufferRegion.invalidate` method is called.
|
||||
|
||||
:Parameters:
|
||||
`start` : int
|
||||
Offset into the buffer to map from, in bytes
|
||||
`size` : int
|
||||
Size of the buffer region to map, in bytes
|
||||
`ptr_type` : ctypes pointer type
|
||||
Pointer type describing the array format to create
|
||||
|
||||
:rtype: :py:class:`AbstractBufferRegion`
|
||||
"""
|
||||
raise NotImplementedError('abstract')
|
||||
|
||||
|
||||
class BufferObject(AbstractBuffer):
|
||||
"""Lightweight representation of an OpenGL Buffer Object.
|
||||
|
||||
@ -222,27 +194,28 @@ class BufferObject(AbstractBuffer):
|
||||
return f"{self.__class__.__name__}(id={self.id}, size={self.size})"
|
||||
|
||||
|
||||
class MappableBufferObject(BufferObject, AbstractMappable):
|
||||
class AttributeBufferObject(BufferObject):
|
||||
"""A buffer with system-memory backed store.
|
||||
|
||||
Updates to the data via `set_data`, `set_data_region` and `map` will be
|
||||
held in local memory until `bind` is called. The advantage is that fewer
|
||||
OpenGL calls are needed, increasing performance.
|
||||
|
||||
There may also be less performance penalty for resizing this buffer.
|
||||
|
||||
Updates to data via :py:meth:`map` are committed immediately.
|
||||
Updates to the data via `set_data` and `set_data_region` will be held
|
||||
in local memory until `buffer_data` is called. The advantage is that
|
||||
fewer OpenGL calls are needed, which can increasing performance at the
|
||||
expense of system memory.
|
||||
"""
|
||||
def __init__(self, size, usage=GL_DYNAMIC_DRAW):
|
||||
super(MappableBufferObject, self).__init__(size, usage)
|
||||
def __init__(self, size, attribute, usage=GL_DYNAMIC_DRAW):
|
||||
super().__init__(size, usage)
|
||||
self._size = size
|
||||
self.data = (ctypes.c_byte * size)()
|
||||
self.data_ptr = ctypes.addressof(self.data)
|
||||
self._dirty_min = sys.maxsize
|
||||
self._dirty_max = 0
|
||||
|
||||
def bind(self):
|
||||
# Commit pending data
|
||||
super(MappableBufferObject, self).bind()
|
||||
self.attribute_stride = attribute.stride
|
||||
self.attribute_count = attribute.count
|
||||
self.attribute_ctype = attribute.c_type
|
||||
|
||||
def bind(self, target=GL_ARRAY_BUFFER):
|
||||
super().bind(target)
|
||||
size = self._dirty_max - self._dirty_min
|
||||
if size > 0:
|
||||
if size == self.size:
|
||||
@ -253,7 +226,7 @@ class MappableBufferObject(BufferObject, AbstractMappable):
|
||||
self._dirty_max = 0
|
||||
|
||||
def set_data(self, data):
|
||||
super(MappableBufferObject, self).set_data(data)
|
||||
super().set_data(data)
|
||||
ctypes.memmove(self.data, data, self.size)
|
||||
self._dirty_min = 0
|
||||
self._dirty_max = self.size
|
||||
@ -263,17 +236,16 @@ class MappableBufferObject(BufferObject, AbstractMappable):
|
||||
self._dirty_min = min(start, self._dirty_min)
|
||||
self._dirty_max = max(start + length, self._dirty_max)
|
||||
|
||||
def map(self, invalidate=False):
|
||||
self._dirty_min = 0
|
||||
self._dirty_max = self.size
|
||||
return self.data
|
||||
@lru_cache(maxsize=None)
|
||||
def get_region(self, start, count):
|
||||
|
||||
def unmap(self):
|
||||
pass
|
||||
byte_start = self.attribute_stride * start # byte offset
|
||||
byte_size = self.attribute_stride * count # number of bytes
|
||||
array_count = self.attribute_count * count # number of values
|
||||
|
||||
def get_region(self, start, size, ptr_type):
|
||||
array = ctypes.cast(self.data_ptr + start, ptr_type).contents
|
||||
return BufferObjectRegion(self, start, start + size, array)
|
||||
ptr_type = ctypes.POINTER(self.attribute_ctype * array_count)
|
||||
array = ctypes.cast(self.data_ptr + byte_start, ptr_type).contents
|
||||
return BufferObjectRegion(self, byte_start, byte_start + byte_size, array)
|
||||
|
||||
def resize(self, size):
|
||||
data = (ctypes.c_byte * size)()
|
||||
@ -289,6 +261,8 @@ class MappableBufferObject(BufferObject, AbstractMappable):
|
||||
self._dirty_min = sys.maxsize
|
||||
self._dirty_max = 0
|
||||
|
||||
self.get_region.cache_clear()
|
||||
|
||||
|
||||
class BufferObjectRegion:
|
||||
"""A mapped region of a MappableBufferObject."""
|
||||
|
@ -23,11 +23,9 @@ primitives of the same OpenGL primitive mode.
|
||||
|
||||
import ctypes
|
||||
|
||||
import pyglet
|
||||
|
||||
from pyglet.gl import *
|
||||
from pyglet.graphics import allocation, shader, vertexarray
|
||||
from pyglet.graphics.vertexbuffer import BufferObject, MappableBufferObject
|
||||
from pyglet.graphics.vertexbuffer import BufferObject, AttributeBufferObject
|
||||
|
||||
|
||||
def _nearest_pow2(v):
|
||||
@ -66,6 +64,22 @@ _gl_types = {
|
||||
}
|
||||
|
||||
|
||||
def _make_attribute_property(attribute):
|
||||
|
||||
attrname = attribute.name
|
||||
|
||||
def _attribute_getter(self):
|
||||
region = attribute.get_region(attribute.buffer, self.start, self.count)
|
||||
region.invalidate()
|
||||
return region.array
|
||||
|
||||
def _attribute_setter(self, values):
|
||||
getattr(self, attrname)[:] = values
|
||||
|
||||
return property(_attribute_getter, _attribute_setter)
|
||||
|
||||
|
||||
|
||||
class VertexDomain:
|
||||
"""Management of a set of vertex lists.
|
||||
|
||||
@ -80,8 +94,10 @@ class VertexDomain:
|
||||
self.attribute_meta = attribute_meta
|
||||
self.allocator = allocation.Allocator(self._initial_count)
|
||||
|
||||
self.attributes = []
|
||||
self.buffer_attributes = [] # list of (buffer, attributes)
|
||||
self.attribute_names = {} # name: attribute
|
||||
self.buffer_attributes = [] # list of (buffer, attributes)
|
||||
|
||||
self._property_dict = {} # name: property(_getter, _setter)
|
||||
|
||||
for name, meta in attribute_meta.items():
|
||||
assert meta['format'][0] in _gl_types, f"'{meta['format']}' is not a valid atrribute format for '{name}'."
|
||||
@ -90,14 +106,19 @@ class VertexDomain:
|
||||
gl_type = _gl_types[meta['format'][0]]
|
||||
normalize = 'n' in meta['format']
|
||||
attribute = shader.Attribute(name, location, count, gl_type, normalize)
|
||||
self.attributes.append(attribute)
|
||||
self.attribute_names[attribute.name] = attribute
|
||||
|
||||
# Create buffer:
|
||||
attribute.buffer = MappableBufferObject(attribute.stride * self.allocator.capacity)
|
||||
attribute.buffer.element_size = attribute.stride
|
||||
attribute.buffer.attributes = (attribute,)
|
||||
attribute.buffer = AttributeBufferObject(attribute.stride * self.allocator.capacity, attribute)
|
||||
|
||||
self.buffer_attributes.append((attribute.buffer, (attribute,)))
|
||||
|
||||
# Create custom property to be used in the VertexList:
|
||||
self._property_dict[attribute.name] = _make_attribute_property(attribute)
|
||||
|
||||
# Make a custom VertexList class w/ properties for each attribute in the ShaderProgram:
|
||||
self._vertexlist_class = type("VertexList", (VertexList,), self._property_dict)
|
||||
|
||||
self.vao = vertexarray.VertexArray()
|
||||
self.vao.bind()
|
||||
for buffer, attributes in self.buffer_attributes:
|
||||
@ -107,20 +128,6 @@ class VertexDomain:
|
||||
attribute.set_pointer(buffer.ptr)
|
||||
self.vao.unbind()
|
||||
|
||||
# Create named attributes for each attribute
|
||||
self.attribute_names = {}
|
||||
for attribute in self.attributes:
|
||||
self.attribute_names[attribute.name] = attribute
|
||||
|
||||
def __del__(self):
|
||||
# Break circular refs that Python GC seems to miss even when forced
|
||||
# collection.
|
||||
for attribute in self.attributes:
|
||||
try:
|
||||
del attribute.buffer
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def safe_alloc(self, count):
|
||||
"""Allocate vertices, resizing the buffers if necessary."""
|
||||
try:
|
||||
@ -129,7 +136,7 @@ class VertexDomain:
|
||||
capacity = _nearest_pow2(e.requested_capacity)
|
||||
self.version += 1
|
||||
for buffer, _ in self.buffer_attributes:
|
||||
buffer.resize(capacity * buffer.element_size)
|
||||
buffer.resize(capacity * buffer.attribute_stride)
|
||||
self.allocator.set_capacity(capacity)
|
||||
return self.allocator.alloc(count)
|
||||
|
||||
@ -141,7 +148,7 @@ class VertexDomain:
|
||||
capacity = _nearest_pow2(e.requested_capacity)
|
||||
self.version += 1
|
||||
for buffer, _ in self.buffer_attributes:
|
||||
buffer.resize(capacity * buffer.element_size)
|
||||
buffer.resize(capacity * buffer.attribute_stride)
|
||||
self.allocator.set_capacity(capacity)
|
||||
return self.allocator.realloc(start, count, new_count)
|
||||
|
||||
@ -157,7 +164,7 @@ class VertexDomain:
|
||||
:rtype: :py:class:`VertexList`
|
||||
"""
|
||||
start = self.safe_alloc(count)
|
||||
return VertexList(self, start, count)
|
||||
return self._vertexlist_class(self, start, count)
|
||||
|
||||
def draw(self, mode):
|
||||
"""Draw all vertices in the domain.
|
||||
@ -221,8 +228,6 @@ class VertexList:
|
||||
self.domain = domain
|
||||
self.start = start
|
||||
self.count = count
|
||||
self._caches = {}
|
||||
self._cache_versions = {}
|
||||
|
||||
def draw(self, mode):
|
||||
"""Draw this vertex list in the given OpenGL mode.
|
||||
@ -247,7 +252,7 @@ class VertexList:
|
||||
new_start = self.domain.safe_realloc(self.start, self.count, count)
|
||||
if new_start != self.start:
|
||||
# Copy contents to new location
|
||||
for attribute in self.domain.attributes:
|
||||
for attribute in self.domain.attribute_names.values():
|
||||
old = attribute.get_region(attribute.buffer, self.start, self.count)
|
||||
new = attribute.get_region(attribute.buffer, new_start, self.count)
|
||||
new.array[:] = old.array[:]
|
||||
@ -255,9 +260,6 @@ class VertexList:
|
||||
self.start = new_start
|
||||
self.count = count
|
||||
|
||||
for version in self._cache_versions:
|
||||
self._cache_versions[version] = None
|
||||
|
||||
def delete(self):
|
||||
"""Delete this group."""
|
||||
self.domain.allocator.dealloc(self.start, self.count)
|
||||
@ -287,33 +289,10 @@ class VertexList:
|
||||
self.domain = domain
|
||||
self.start = new_start
|
||||
|
||||
for version in self._cache_versions:
|
||||
self._cache_versions[version] = None
|
||||
|
||||
def set_attribute_data(self, name, data):
|
||||
attribute = self.domain.attribute_names[name]
|
||||
attribute.set_region(attribute.buffer, self.start, self.count, data)
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""dynamic access to vertex attributes, for backwards compatibility.
|
||||
"""
|
||||
domain = self.domain
|
||||
if self._cache_versions.get(name, None) != domain.version:
|
||||
attribute = domain.attribute_names[name]
|
||||
self._caches[name] = attribute.get_region(attribute.buffer, self.start, self.count)
|
||||
self._cache_versions[name] = domain.version
|
||||
|
||||
region = self._caches[name]
|
||||
region.invalidate()
|
||||
return region.array
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
# Allow setting vertex attributes directly without overwriting them:
|
||||
if 'domain' in self.__dict__ and name in self.__dict__['domain'].attribute_names:
|
||||
getattr(self, name)[:] = value
|
||||
return
|
||||
super().__setattr__(name, value)
|
||||
|
||||
|
||||
class IndexedVertexDomain(VertexDomain):
|
||||
"""Management of a set of indexed vertex lists.
|
||||
@ -337,6 +316,9 @@ class IndexedVertexDomain(VertexDomain):
|
||||
self.index_buffer.bind_to_index_buffer()
|
||||
self.vao.unbind()
|
||||
|
||||
# Make a custom VertexList class w/ properties for each attribute in the ShaderProgram:
|
||||
self._vertexlist_class = type("IndexedVertexList", (IndexedVertexList,), self._property_dict)
|
||||
|
||||
def safe_index_alloc(self, count):
|
||||
"""Allocate indices, resizing the buffers if necessary."""
|
||||
try:
|
||||
@ -371,7 +353,7 @@ class IndexedVertexDomain(VertexDomain):
|
||||
"""
|
||||
start = self.safe_alloc(count)
|
||||
index_start = self.safe_index_alloc(index_count)
|
||||
return IndexedVertexList(self, start, count, index_start, index_count)
|
||||
return self._vertexlist_class(self, start, count, index_start, index_count)
|
||||
|
||||
def get_index_region(self, start, count):
|
||||
"""Get a data from a region of the index buffer.
|
||||
|
@ -18,7 +18,7 @@ Interfaces can define methods::
|
||||
...
|
||||
]
|
||||
|
||||
Only use STDMETHOD or METHOD for the method types (not ordinary ctypes
|
||||
Only use METHOD, STDMETHOD or VOIDMETHOD for the method types (not ordinary ctypes
|
||||
function types). The 'this' pointer is bound automatically... e.g., call::
|
||||
|
||||
device = IDirectSound8()
|
||||
@ -50,7 +50,7 @@ class GUID(ctypes.Structure):
|
||||
('Data1', ctypes.c_ulong),
|
||||
('Data2', ctypes.c_ushort),
|
||||
('Data3', ctypes.c_ushort),
|
||||
('Data4', ctypes.c_ubyte * 8)
|
||||
('Data4', ctypes.c_ubyte * 8),
|
||||
]
|
||||
|
||||
def __init__(self, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8):
|
||||
@ -64,11 +64,6 @@ class GUID(ctypes.Structure):
|
||||
return 'GUID(%x, %x, %x, %x, %x, %x, %x, %x, %x, %x, %x)' % (
|
||||
self.Data1, self.Data2, self.Data3, b1, b2, b3, b4, b5, b6, b7, b8)
|
||||
|
||||
def __cmp__(self, other):
|
||||
if isinstance(other, GUID):
|
||||
return ctypes.cmp(bytes(self), bytes(other))
|
||||
return -1
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, GUID) and bytes(self) == bytes(other)
|
||||
|
||||
@ -80,6 +75,10 @@ LPGUID = ctypes.POINTER(GUID)
|
||||
IID = GUID
|
||||
REFIID = ctypes.POINTER(IID)
|
||||
|
||||
S_OK = 0x00000000
|
||||
E_NOTIMPL = 0x80004001
|
||||
E_NOINTERFACE = 0x80004002
|
||||
|
||||
|
||||
class METHOD:
|
||||
"""COM method."""
|
||||
@ -88,244 +87,147 @@ class METHOD:
|
||||
self.restype = restype
|
||||
self.argtypes = args
|
||||
|
||||
def get_field(self):
|
||||
# ctypes caches WINFUNCTYPE's so this should be ok.
|
||||
return ctypes.WINFUNCTYPE(self.restype, *self.argtypes)
|
||||
self.prototype = ctypes.WINFUNCTYPE(self.restype, *self.argtypes)
|
||||
self.direct_prototype = ctypes.WINFUNCTYPE(self.restype, ctypes.c_void_p, *self.argtypes)
|
||||
|
||||
def get_com_proxy(self, i, name):
|
||||
return self.prototype(i, name)
|
||||
|
||||
|
||||
class STDMETHOD(METHOD):
|
||||
"""COM method with HRESULT return value."""
|
||||
|
||||
def __init__(self, *args):
|
||||
super(STDMETHOD, self).__init__(ctypes.HRESULT, *args)
|
||||
super().__init__(ctypes.HRESULT, *args)
|
||||
|
||||
|
||||
class COMMethodInstance:
|
||||
"""Binds a COM interface method."""
|
||||
class VOIDMETHOD(METHOD):
|
||||
"""COM method with no return value."""
|
||||
|
||||
def __init__(self, name, i, method):
|
||||
self.name = name
|
||||
self.i = i
|
||||
self.method = method
|
||||
|
||||
def __get__(self, obj, tp):
|
||||
if obj is not None:
|
||||
def _call(*args):
|
||||
assert _debug_com('COM: #{} IN {}({}, {})'.format(self.i, self.name, obj.__class__.__name__, args))
|
||||
ret = self.method.get_field()(self.i, self.name)(obj, *args)
|
||||
assert _debug_com('COM: #{} OUT {}({}, {})'.format(self.i, self.name, obj.__class__.__name__, args))
|
||||
assert _debug_com('COM: RETURN {}'.format(ret))
|
||||
return ret
|
||||
|
||||
return _call
|
||||
|
||||
raise AttributeError()
|
||||
def __init__(self, *args):
|
||||
super().__init__(None, *args)
|
||||
|
||||
|
||||
class COMInterface(ctypes.Structure):
|
||||
"""Dummy struct to serve as the type of all COM pointers."""
|
||||
_fields_ = [
|
||||
('lpVtbl', ctypes.c_void_p),
|
||||
]
|
||||
_DummyPointerType = ctypes.POINTER(ctypes.c_int)
|
||||
_PointerMeta = type(_DummyPointerType)
|
||||
_StructMeta = type(ctypes.Structure)
|
||||
|
||||
|
||||
class InterfacePtrMeta(type(ctypes.POINTER(COMInterface))):
|
||||
"""Allows interfaces to be subclassed as ctypes POINTER and expects to be populated with data from a COM object.
|
||||
TODO: Phase this out and properly use POINTER(Interface) where applicable.
|
||||
"""
|
||||
class _InterfaceMeta(_StructMeta):
|
||||
def __new__(cls, name, bases, dct, /, create_pointer_type=True):
|
||||
if len(bases) > 1:
|
||||
assert _debug_com(f"Ignoring {len(bases) - 1} bases on {name}")
|
||||
bases = (bases[0],)
|
||||
|
||||
if not '_methods_' in dct:
|
||||
dct['_methods_'] = ()
|
||||
|
||||
inh_methods = []
|
||||
if bases[0] is not ctypes.Structure: # Method does not exist for first definition below
|
||||
for interface_type in (bases[0].get_interface_inheritance()):
|
||||
inh_methods.extend(interface_type.__dict__['_methods_'])
|
||||
|
||||
inh_methods = tuple(inh_methods)
|
||||
new_methods = tuple(dct['_methods_'])
|
||||
|
||||
vtbl_own_offset = len(inh_methods)
|
||||
|
||||
all_methods = tuple(inh_methods) + new_methods
|
||||
for i, (method_name, mt) in enumerate(all_methods):
|
||||
assert _debug_com(f"{name}[{i}]: {method_name}: "
|
||||
f"{(', '.join(t.__name__ for t in mt.argtypes) or 'void')} -> "
|
||||
f"{'void' if mt.restype is None else mt.restype.__name__}")
|
||||
|
||||
vtbl_struct_type = _StructMeta(f"Vtable_{name}",
|
||||
(ctypes.Structure,),
|
||||
{'_fields_': [(n, x.direct_prototype) for n, x in all_methods]})
|
||||
dct['_vtbl_struct_type'] = vtbl_struct_type
|
||||
dct['vtbl_own_offset'] = vtbl_own_offset
|
||||
|
||||
dct['_fields_'] = (('vtbl_ptr', ctypes.POINTER(vtbl_struct_type)),)
|
||||
|
||||
res_type = super().__new__(cls, name, bases, dct)
|
||||
if create_pointer_type:
|
||||
# If we're not being created from a pInterface subclass as helper Interface (so likely
|
||||
# being explicitly defined from user code for later use), create the special
|
||||
# pInterface pointer subclass so it registers itself into the pointer cache
|
||||
_pInterfaceMeta(f"p{name}", (ctypes.POINTER(bases[0]),), {'_type_': res_type})
|
||||
|
||||
return res_type
|
||||
|
||||
|
||||
class _pInterfaceMeta(_PointerMeta):
|
||||
def __new__(cls, name, bases, dct):
|
||||
methods = []
|
||||
for base in bases[::-1]:
|
||||
methods.extend(base.__dict__.get('_methods_', ()))
|
||||
methods.extend(dct.get('_methods_', ()))
|
||||
# Interfaces can also be declared by inheritance of pInterface subclasses.
|
||||
# If this happens, create the interface and then become pointer to its struct.
|
||||
|
||||
for i, (n, method) in enumerate(methods):
|
||||
dct[n] = COMMethodInstance(n, i, method)
|
||||
target = dct.get('_type_', None)
|
||||
# If we weren't created due to an Interface subclass definition (don't have a _type_),
|
||||
# just define that Interface subclass from our base's _type_
|
||||
if target is None:
|
||||
interface_base = bases[0]._type_
|
||||
|
||||
dct['_type_'] = COMInterface
|
||||
# Create corresponding interface type and then set it as target
|
||||
target = _InterfaceMeta(f"_{name}_HelperInterface",
|
||||
(interface_base,),
|
||||
{'_methods_': dct.get('_methods_', ())},
|
||||
create_pointer_type=False)
|
||||
dct['_type_'] = target
|
||||
|
||||
return super(InterfacePtrMeta, cls).__new__(cls, name, bases, dct)
|
||||
# Create method proxies that will forward ourselves into the interface's methods
|
||||
for i, (method_name, method) in enumerate(target._methods_):
|
||||
m = method.get_com_proxy(i + target.vtbl_own_offset, method_name)
|
||||
def pinterface_method_forward(self, *args, _m=m, _i=i):
|
||||
assert _debug_com(f'Calling COM {_i} of {target.__name__} ({_m}) through '
|
||||
f'pointer: ({", ".join(map(repr, (self, *args)))})')
|
||||
return _m(self, *args)
|
||||
dct[method_name] = pinterface_method_forward
|
||||
|
||||
pointer_type = super().__new__(cls, name, bases, dct)
|
||||
|
||||
class pInterface(ctypes.POINTER(COMInterface), metaclass=InterfacePtrMeta):
|
||||
"""Base COM interface pointer."""
|
||||
|
||||
|
||||
class COMInterfaceMeta(type):
|
||||
"""This differs in the original as an implemented interface object, not a POINTER object.
|
||||
Used when the user must implement their own functions within an interface rather than
|
||||
being created and generated by the COM object itself. The types are automatically inserted in the ctypes type
|
||||
cache so it can recognize the type arguments.
|
||||
"""
|
||||
|
||||
def __new__(mcs, name, bases, dct):
|
||||
methods = dct.pop("_methods_", None)
|
||||
cls = type.__new__(mcs, name, bases, dct)
|
||||
|
||||
if methods is not None:
|
||||
cls._methods_ = methods
|
||||
|
||||
if not bases:
|
||||
_ptr_bases = (cls, COMPointer)
|
||||
else:
|
||||
_ptr_bases = (cls, ctypes.POINTER(bases[0]))
|
||||
|
||||
# Class type is dynamically created inside __new__ based on metaclass inheritence; update ctypes cache manually.
|
||||
# Hack selves into the ctypes pointer cache so all uses of `ctypes.POINTER` on the
|
||||
# interface type will yield it instead of the inflexible standard pointer type.
|
||||
# NOTE: This is done pretty much exclusively to help convert COMObjects.
|
||||
# Some additional work from callers like
|
||||
# RegisterCallback(callback_obj.as_interface(ICallback))
|
||||
# instead of
|
||||
# RegisterCallback(callback_obj)
|
||||
# could make it obsolete.
|
||||
from ctypes import _pointer_type_cache
|
||||
_pointer_type_cache[cls] = type(COMPointer)("POINTER({})".format(cls.__name__),
|
||||
_ptr_bases,
|
||||
{"__interface__": cls})
|
||||
_pointer_type_cache[target] = pointer_type
|
||||
|
||||
return cls
|
||||
return pointer_type
|
||||
|
||||
def __get_subclassed_methodcount(self):
|
||||
"""Returns the amount of COM methods in all subclasses to determine offset of methods.
|
||||
Order must be exact from the source when calling COM methods.
|
||||
|
||||
class Interface(ctypes.Structure, metaclass=_InterfaceMeta, create_pointer_type=False):
|
||||
@classmethod
|
||||
def get_interface_inheritance(cls):
|
||||
"""Returns the types of all interfaces implemented by this interface, up to but not
|
||||
including the base `Interface`.
|
||||
`Interface` does not represent an actual interface, but merely the base concept of
|
||||
them, so viewing it as part of an interface's inheritance chain is meaningless.
|
||||
"""
|
||||
try:
|
||||
result = 0
|
||||
for itf in self.mro()[1:-1]:
|
||||
result += len(itf.__dict__["_methods_"])
|
||||
return result
|
||||
except KeyError as err:
|
||||
(name,) = err.args
|
||||
if name == "_methods_":
|
||||
raise TypeError("Interface '{}' requires a _methods_ attribute.".format(itf.__name__))
|
||||
raise
|
||||
return cls.__mro__[:cls.__mro__.index(Interface)]
|
||||
|
||||
|
||||
class COMPointerMeta(type(ctypes.c_void_p), COMInterfaceMeta):
|
||||
"""Required to prevent metaclass conflicts with inheritance."""
|
||||
|
||||
|
||||
class COMPointer(ctypes.c_void_p, metaclass=COMPointerMeta):
|
||||
"""COM Pointer base, could use c_void_p but need to override from_param ."""
|
||||
class pInterface(_DummyPointerType, metaclass=_pInterfaceMeta):
|
||||
_type_ = Interface
|
||||
|
||||
@classmethod
|
||||
def from_param(cls, obj):
|
||||
"""Allows obj to return ctypes pointers, even if its base is not a ctype.
|
||||
In this case, all we simply want is a ctypes pointer matching the cls interface from the obj.
|
||||
"""
|
||||
if obj is None:
|
||||
return
|
||||
"""When dealing with a COMObject, pry a fitting interface out of it"""
|
||||
|
||||
try:
|
||||
ptr_dct = obj._pointers
|
||||
except AttributeError:
|
||||
raise Exception("Interface method argument specified incorrectly, or passed wrong argument.", cls)
|
||||
else:
|
||||
try:
|
||||
return ptr_dct[cls.__interface__]
|
||||
except KeyError:
|
||||
raise TypeError("Interface {} doesn't have a pointer in this class.".format(cls.__name__))
|
||||
if not isinstance(obj, COMObject):
|
||||
return obj
|
||||
|
||||
return obj.as_interface(cls._type_)
|
||||
|
||||
|
||||
def _missing_impl(interface_name, method_name):
|
||||
"""Functions that are not implemented use this to prevent errors when called."""
|
||||
|
||||
def missing_cb_func(*args):
|
||||
"""Return E_NOTIMPL because the method is not implemented."""
|
||||
assert _debug_com("Undefined method: {0} was called in interface: {1}".format(method_name, interface_name))
|
||||
return 0
|
||||
|
||||
return missing_cb_func
|
||||
|
||||
|
||||
def _found_impl(interface_name, method_name, method_func):
|
||||
"""If a method was found in class, we can set it as a callback."""
|
||||
|
||||
def cb_func(*args, **kw):
|
||||
try:
|
||||
result = method_func(*args, **kw)
|
||||
except Exception as err:
|
||||
raise err
|
||||
|
||||
if not result: # QOL so callbacks don't need to specify a return for assumed OK's.
|
||||
return 0
|
||||
|
||||
return result
|
||||
|
||||
return cb_func
|
||||
|
||||
|
||||
def _make_callback_func(interface, name, method_func):
|
||||
"""Create a callback function for ctypes if possible."""
|
||||
if method_func is None:
|
||||
return _missing_impl(interface, name)
|
||||
|
||||
return _found_impl(interface, name, method_func)
|
||||
|
||||
|
||||
# Store structures with same fields to prevent duplicate table creations.
|
||||
_cached_structures = {}
|
||||
|
||||
|
||||
def create_vtbl_structure(fields, interface):
|
||||
"""Create virtual table structure with fields for use in COM's."""
|
||||
try:
|
||||
return _cached_structures[fields]
|
||||
except KeyError:
|
||||
Vtbl = type("Vtbl_{}".format(interface.__name__), (ctypes.Structure,), {"_fields_": fields})
|
||||
_cached_structures[fields] = Vtbl
|
||||
return Vtbl
|
||||
|
||||
|
||||
class COMObject:
|
||||
"""A base class for defining a COM object for use with callbacks and custom implementations."""
|
||||
_interfaces_ = []
|
||||
|
||||
def __new__(cls, *args, **kw):
|
||||
new_cls = super(COMObject, cls).__new__(cls)
|
||||
assert len(cls._interfaces_) > 0, "Atleast one interface must be defined to use a COMObject."
|
||||
new_cls._pointers = {}
|
||||
new_cls.__create_interface_pointers()
|
||||
return new_cls
|
||||
|
||||
def __create_interface_pointers(cls):
|
||||
"""Create a custom ctypes structure to handle COM functions in a COM Object."""
|
||||
interfaces = tuple(cls._interfaces_)
|
||||
for itf in interfaces[::-1]:
|
||||
methods = []
|
||||
fields = []
|
||||
for interface in itf.__mro__[-2::-1]:
|
||||
for method in interface._methods_:
|
||||
name, com_method = method
|
||||
|
||||
found_method = getattr(cls, name, None)
|
||||
mth = _make_callback_func(itf.__name__, name, found_method)
|
||||
|
||||
proto = ctypes.WINFUNCTYPE(com_method.restype, *com_method.argtypes)
|
||||
|
||||
fields.append((name, proto))
|
||||
methods.append(proto(mth))
|
||||
|
||||
# Make a structure dynamically with the fields given.
|
||||
itf_structure = create_vtbl_structure(tuple(fields), interface)
|
||||
|
||||
# Assign the methods to the fields
|
||||
vtbl = itf_structure(*methods)
|
||||
|
||||
cls._pointers[itf] = ctypes.pointer(ctypes.pointer(vtbl))
|
||||
|
||||
@property
|
||||
def pointers(self):
|
||||
"""Returns pointers to the implemented interfaces in this COMObject. Read-only.
|
||||
|
||||
:type: dict
|
||||
"""
|
||||
return self._pointers
|
||||
|
||||
class Interface(metaclass=COMInterfaceMeta):
|
||||
_methods_ = []
|
||||
|
||||
|
||||
class IUnknown(metaclass=COMInterfaceMeta):
|
||||
"""These methods are not implemented by default yet. Strictly for COM method ordering."""
|
||||
class IUnknown(Interface):
|
||||
_methods_ = [
|
||||
('QueryInterface', STDMETHOD(ctypes.c_void_p, REFIID, ctypes.c_void_p)),
|
||||
('AddRef', METHOD(ctypes.c_int, ctypes.c_void_p)),
|
||||
('Release', METHOD(ctypes.c_int, ctypes.c_void_p))
|
||||
('QueryInterface', STDMETHOD(REFIID, ctypes.c_void_p)),
|
||||
('AddRef', METHOD(ctypes.c_int)),
|
||||
('Release', METHOD(ctypes.c_int)),
|
||||
]
|
||||
|
||||
|
||||
@ -333,5 +235,163 @@ class pIUnknown(pInterface):
|
||||
_methods_ = [
|
||||
('QueryInterface', STDMETHOD(REFIID, ctypes.c_void_p)),
|
||||
('AddRef', METHOD(ctypes.c_int)),
|
||||
('Release', METHOD(ctypes.c_int))
|
||||
('Release', METHOD(ctypes.c_int)),
|
||||
]
|
||||
|
||||
|
||||
def _missing_impl(interface_name, method_name):
|
||||
"""Create a callback returning E_NOTIMPL for methods not present on a COMObject."""
|
||||
|
||||
def missing_cb_func(*_):
|
||||
assert _debug_com(f"Non-implemented method {method_name} called in {interface_name}")
|
||||
return E_NOTIMPL
|
||||
|
||||
return missing_cb_func
|
||||
|
||||
|
||||
def _found_impl(interface_name, method_name, method_func, self_distance):
|
||||
"""If a method was found in class, create a callback extracting self from the struct
|
||||
pointer.
|
||||
"""
|
||||
|
||||
def self_extracting_cb_func(p, *args):
|
||||
assert _debug_com(f"COMObject method {method_name} called through interface {interface_name}")
|
||||
self = ctypes.cast(p + self_distance, ctypes.POINTER(ctypes.py_object)).contents.value
|
||||
result = method_func(self, *args)
|
||||
# Assume no return statement translates to success
|
||||
return S_OK if result is None else result
|
||||
|
||||
return self_extracting_cb_func
|
||||
|
||||
|
||||
def _adjust_impl(interface_name, method_name, original_method, offset):
|
||||
"""A method implemented in a previous interface modifies the COMOboject pointer so it
|
||||
corresponds to an earlier interface and passes it on to the actual implementation.
|
||||
"""
|
||||
|
||||
def adjustor_cb_func(p, *args):
|
||||
assert _debug_com(f"COMObject method {method_name} called through interface "
|
||||
f"{interface_name}, adjusting pointer by {offset}")
|
||||
return original_method(p + offset, *args)
|
||||
|
||||
return adjustor_cb_func
|
||||
|
||||
|
||||
class COMObject:
|
||||
"""A COMObject for implementing C callbacks in Python.
|
||||
Specify the interface types it supports in `_interfaces_`, and any methods to be implemented
|
||||
by those interfaces as standard python methods. If the names match, they will be run as
|
||||
callbacks with all arguments supplied as the types specified in the corresponding interface,
|
||||
and `self` available as usual.
|
||||
Remember to call `super().__init__()`.
|
||||
|
||||
COMObjects can be passed to ctypes functions directly as long as the corresponding argtype is
|
||||
an `Interface` pointer, or a `pInterface` subclass.
|
||||
|
||||
IUnknown's methods will be autogenerated in case IUnknown is implemented.
|
||||
"""
|
||||
|
||||
def __init_subclass__(cls, /, **kwargs):
|
||||
super().__init_subclass__(**kwargs)
|
||||
|
||||
implemented_leaf_interfaces = cls.__dict__.get('_interfaces_', ())
|
||||
if not implemented_leaf_interfaces:
|
||||
raise TypeError("At least one interface must be defined to use a COMObject")
|
||||
|
||||
for interface_type in implemented_leaf_interfaces:
|
||||
for other in implemented_leaf_interfaces:
|
||||
if interface_type is other:
|
||||
continue
|
||||
if issubclass(interface_type, other):
|
||||
raise TypeError("Only specify the leaf interfaces")
|
||||
|
||||
# Sanity check done
|
||||
|
||||
_ptr_size = ctypes.sizeof(ctypes.c_void_p)
|
||||
|
||||
_vtbl_pointers = []
|
||||
implemented_methods = {}
|
||||
|
||||
# Map all leaf and inherited interfaces to the offset of the vtable containing
|
||||
# their implementations
|
||||
_interface_to_vtbl_offset = {}
|
||||
for i, interface_type in enumerate(implemented_leaf_interfaces):
|
||||
bases = interface_type.get_interface_inheritance()
|
||||
for base in bases:
|
||||
if base not in _interface_to_vtbl_offset:
|
||||
_interface_to_vtbl_offset[base] = i * _ptr_size
|
||||
|
||||
if IUnknown in _interface_to_vtbl_offset:
|
||||
def QueryInterface(self, iid_ptr, res_ptr):
|
||||
ctypes.cast(res_ptr, ctypes.POINTER(ctypes.c_void_p))[0] = 0
|
||||
return E_NOINTERFACE
|
||||
|
||||
def AddRef(self):
|
||||
self._vrefcount += 1
|
||||
return self._vrefcount
|
||||
|
||||
def Release(self):
|
||||
if self._vrefcount <= 0:
|
||||
assert _debug_com(
|
||||
f"COMObject {self}: Release while refcount was {self._vrefcount}"
|
||||
)
|
||||
self._vrefcount -= 1
|
||||
return self._vrefcount
|
||||
|
||||
cls.QueryInterface = QueryInterface
|
||||
cls.AddRef = AddRef
|
||||
cls.Release = Release
|
||||
|
||||
for i, interface_type in enumerate(implemented_leaf_interfaces):
|
||||
wrappers = []
|
||||
|
||||
for method_name, method_type in interface_type._vtbl_struct_type._fields_:
|
||||
if method_name in implemented_methods:
|
||||
# Method is already implemented on a previous interface; redirect to it
|
||||
# See https://devblogs.microsoft.com/oldnewthing/20040206-00/?p=40723
|
||||
# NOTE: Never tested, might be totally wrong
|
||||
func, implementing_vtbl_idx = implemented_methods[method_name]
|
||||
mth = _adjust_impl(interface_type.__name__,
|
||||
method_name,
|
||||
func,
|
||||
(implementing_vtbl_idx - i) * _ptr_size)
|
||||
|
||||
else:
|
||||
if (found_method := getattr(cls, method_name, None)) is None:
|
||||
mth = _missing_impl(interface_type.__name__, method_name)
|
||||
else:
|
||||
mth = _found_impl(interface_type.__name__,
|
||||
method_name,
|
||||
found_method,
|
||||
(len(implemented_leaf_interfaces) - i) * _ptr_size)
|
||||
|
||||
implemented_methods[method_name] = (mth, i)
|
||||
|
||||
wrappers.append(method_type(mth))
|
||||
|
||||
vtbl = interface_type._vtbl_struct_type(*wrappers)
|
||||
_vtbl_pointers.append(ctypes.pointer(vtbl))
|
||||
|
||||
fields = []
|
||||
for i, itf in enumerate(implemented_leaf_interfaces):
|
||||
fields.append((f'vtbl_ptr_{i}', ctypes.POINTER(itf._vtbl_struct_type)))
|
||||
fields.append(('self_', ctypes.py_object))
|
||||
|
||||
cls._interface_to_vtbl_offset = _interface_to_vtbl_offset
|
||||
cls._vtbl_pointers = _vtbl_pointers
|
||||
cls._struct_type = _StructMeta(f"{cls.__name__}_Struct", (ctypes.Structure,), {'_fields_': fields})
|
||||
|
||||
def __init__(self):
|
||||
self._vrefcount = 1
|
||||
self._struct = self._struct_type(*self._vtbl_pointers, ctypes.py_object(self))
|
||||
|
||||
def as_interface(self, interface_type):
|
||||
# This method ignores the QueryInterface mechanism completely; no GUIDs are
|
||||
# associated with Interfaces on the python side, it can't be supported.
|
||||
# Still works, as so far none of the python-made COMObjects are expected to
|
||||
# support it by any C code.
|
||||
# (Also no need to always implement it, some COMObjects do not inherit from IUnknown.)
|
||||
if (offset := self._interface_to_vtbl_offset.get(interface_type, None)) is None:
|
||||
raise TypeError(f"Does not implement {interface_type}")
|
||||
|
||||
return ctypes.byref(self._struct, offset)
|
||||
|
@ -83,15 +83,15 @@ IID_IMMDeviceEnumerator = com.GUID(0xa95664d2, 0x9614, 0x4f35, 0xa7, 0x46, 0xde,
|
||||
class IMMNotificationClient(com.IUnknown):
|
||||
_methods_ = [
|
||||
('OnDeviceStateChanged',
|
||||
com.METHOD(ctypes.c_void_p, ctypes.c_void_p, LPCWSTR, DWORD)),
|
||||
com.STDMETHOD(LPCWSTR, DWORD)),
|
||||
('OnDeviceAdded',
|
||||
com.METHOD(ctypes.c_void_p, ctypes.c_void_p, LPCWSTR)),
|
||||
com.STDMETHOD(LPCWSTR)),
|
||||
('OnDeviceRemoved',
|
||||
com.METHOD(ctypes.c_void_p, ctypes.c_void_p, LPCWSTR)),
|
||||
com.STDMETHOD(LPCWSTR)),
|
||||
('OnDefaultDeviceChanged',
|
||||
com.METHOD(ctypes.c_void_p, ctypes.c_void_p, EDataFlow, ERole, LPCWSTR)),
|
||||
com.STDMETHOD(EDataFlow, ERole, LPCWSTR)),
|
||||
('OnPropertyValueChanged',
|
||||
com.METHOD(ctypes.c_void_p, ctypes.c_void_p, LPCWSTR, PROPERTYKEY)),
|
||||
com.STDMETHOD(LPCWSTR, PROPERTYKEY)),
|
||||
]
|
||||
|
||||
|
||||
@ -113,7 +113,7 @@ class AudioNotificationCB(com.COMObject):
|
||||
self.audio_devices = audio_devices
|
||||
self._lost = False
|
||||
|
||||
def OnDeviceStateChanged(self, this, pwstrDeviceId, dwNewState):
|
||||
def OnDeviceStateChanged(self, pwstrDeviceId, dwNewState):
|
||||
device = self.audio_devices.get_cached_device(pwstrDeviceId)
|
||||
|
||||
old_state = device.state
|
||||
@ -126,17 +126,17 @@ class AudioNotificationCB(com.COMObject):
|
||||
device.state = dwNewState
|
||||
self.audio_devices.dispatch_event('on_device_state_changed', device, pyglet_old_state, pyglet_new_state)
|
||||
|
||||
def OnDeviceAdded(self, this, pwstrDeviceId):
|
||||
def OnDeviceAdded(self, pwstrDeviceId):
|
||||
dev = self.audio_devices.add_device(pwstrDeviceId)
|
||||
assert _debug(f"Audio device was added {pwstrDeviceId}: {dev}")
|
||||
self.audio_devices.dispatch_event('on_device_added', dev)
|
||||
|
||||
def OnDeviceRemoved(self, this, pwstrDeviceId):
|
||||
def OnDeviceRemoved(self, pwstrDeviceId):
|
||||
dev = self.audio_devices.remove_device(pwstrDeviceId)
|
||||
assert _debug(f"Audio device was removed {pwstrDeviceId} : {dev}")
|
||||
self.audio_devices.dispatch_event('on_device_removed', dev)
|
||||
|
||||
def OnDefaultDeviceChanged(self, this, flow, role, pwstrDeviceId):
|
||||
def OnDefaultDeviceChanged(self, flow, role, pwstrDeviceId):
|
||||
# Only support eConsole role right now
|
||||
if role == 0:
|
||||
if pwstrDeviceId is None:
|
||||
@ -149,7 +149,7 @@ class AudioNotificationCB(com.COMObject):
|
||||
|
||||
self.audio_devices.dispatch_event('on_default_changed', device, pyglet_flow)
|
||||
|
||||
def OnPropertyValueChanged(self, this, pwstrDeviceId, key):
|
||||
def OnPropertyValueChanged(self, pwstrDeviceId, key):
|
||||
pass
|
||||
|
||||
|
||||
|
@ -270,7 +270,6 @@ class IDirectSound(com.pIUnknown):
|
||||
('Initialize',
|
||||
com.STDMETHOD(com.LPGUID)),
|
||||
]
|
||||
_type_ = com.COMInterface
|
||||
|
||||
DirectSoundCreate = lib.DirectSoundCreate
|
||||
DirectSoundCreate.argtypes = \
|
||||
|
@ -192,17 +192,19 @@ XAUDIO2_NO_VIRTUAL_AUDIO_CLIENT = 0x10000 # Used in CreateMasteringVoice to cr
|
||||
class IXAudio2VoiceCallback(com.Interface):
|
||||
_methods_ = [
|
||||
('OnVoiceProcessingPassStart',
|
||||
com.STDMETHOD(UINT32)),
|
||||
com.VOIDMETHOD(UINT32)),
|
||||
('OnVoiceProcessingPassEnd',
|
||||
com.STDMETHOD()),
|
||||
('onStreamEnd',
|
||||
com.STDMETHOD()),
|
||||
('onBufferStart',
|
||||
com.STDMETHOD(ctypes.c_void_p)),
|
||||
com.VOIDMETHOD()),
|
||||
('OnStreamEnd',
|
||||
com.VOIDMETHOD()),
|
||||
('OnBufferStart',
|
||||
com.VOIDMETHOD(ctypes.c_void_p)),
|
||||
('OnBufferEnd',
|
||||
com.STDMETHOD(ctypes.c_void_p)),
|
||||
com.VOIDMETHOD(ctypes.c_void_p)),
|
||||
('OnLoopEnd',
|
||||
com.STDMETHOD(ctypes.c_void_p)),
|
||||
com.VOIDMETHOD(ctypes.c_void_p)),
|
||||
('OnVoiceError',
|
||||
com.VOIDMETHOD(ctypes.c_void_p, HRESULT))
|
||||
]
|
||||
|
||||
|
||||
@ -220,20 +222,9 @@ class XA2SourceCallback(com.COMObject):
|
||||
_interfaces_ = [IXAudio2VoiceCallback]
|
||||
|
||||
def __init__(self, xa2_player):
|
||||
super().__init__()
|
||||
self.xa2_player = xa2_player
|
||||
|
||||
def OnVoiceProcessingPassStart(self, bytesRequired):
|
||||
pass
|
||||
|
||||
def OnVoiceProcessingPassEnd(self):
|
||||
pass
|
||||
|
||||
def onStreamEnd(self):
|
||||
pass
|
||||
|
||||
def onBufferStart(self, pBufferContext):
|
||||
pass
|
||||
|
||||
def OnBufferEnd(self, pBufferContext):
|
||||
"""At the end of playing one buffer, attempt to refill again.
|
||||
Even if the player is out of sources, it needs to be called to purge all buffers.
|
||||
@ -241,10 +232,7 @@ class XA2SourceCallback(com.COMObject):
|
||||
if self.xa2_player:
|
||||
self.xa2_player.refill_source_player()
|
||||
|
||||
def OnLoopEnd(self, this, pBufferContext):
|
||||
pass
|
||||
|
||||
def onVoiceError(self, this, pBufferContext, hresult):
|
||||
def OnVoiceError(self, pBufferContext, hresult):
|
||||
raise Exception("Error occurred during audio playback.", hresult)
|
||||
|
||||
|
||||
@ -362,24 +350,18 @@ class IXAudio2MasteringVoice(IXAudio2Voice):
|
||||
class IXAudio2EngineCallback(com.Interface):
|
||||
_methods_ = [
|
||||
('OnProcessingPassStart',
|
||||
com.METHOD(ctypes.c_void_p)),
|
||||
com.VOIDMETHOD()),
|
||||
('OnProcessingPassEnd',
|
||||
com.METHOD(ctypes.c_void_p)),
|
||||
com.VOIDMETHOD()),
|
||||
('OnCriticalError',
|
||||
com.METHOD(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_ulong)),
|
||||
com.VOIDMETHOD(HRESULT)),
|
||||
]
|
||||
|
||||
|
||||
class XA2EngineCallback(com.COMObject):
|
||||
_interfaces_ = [IXAudio2EngineCallback]
|
||||
|
||||
def OnProcessingPassStart(self):
|
||||
pass
|
||||
|
||||
def OnProcessingPassEnd(self):
|
||||
pass
|
||||
|
||||
def OnCriticalError(self, this, hresult):
|
||||
def OnCriticalError(self, hresult):
|
||||
raise Exception("Critical Error:", hresult)
|
||||
|
||||
|
||||
|
@ -98,7 +98,7 @@ class Caret:
|
||||
|
||||
colors = r, g, b, self._visible_alpha, r, g, b, self._visible_alpha
|
||||
|
||||
self._list = self._group.program.vertex_list(2, gl.GL_LINES, batch, self._group, colors=('Bn', colors))
|
||||
self._list = self._group.program.vertex_list(2, gl.GL_LINES, self._batch, self._group, colors=('Bn', colors))
|
||||
self._ideal_x = None
|
||||
self._ideal_line = None
|
||||
self._next_attributes = {}
|
||||
@ -127,6 +127,7 @@ class Caret:
|
||||
|
||||
Also disconnects the caret from further layout events.
|
||||
"""
|
||||
clock.unschedule(self._blink)
|
||||
self._list.delete()
|
||||
self._layout.remove_handlers(self)
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
import time
|
||||
|
||||
from string import Template
|
||||
from typing import List
|
||||
from lib_not_dr.types.options import Options
|
||||
|
||||
@ -51,11 +52,12 @@ class TimeFormatter(BaseFormatter):
|
||||
name = 'TimeFormatter'
|
||||
|
||||
time_format: str = '%Y-%m-%d %H:%M:%S'
|
||||
msec_time_format: str = '{}-{:03d}'
|
||||
|
||||
@classmethod
|
||||
def _info(cls) -> str:
|
||||
return cls.add_info('log_time', 'when the log message was created', 'The time format string'
|
||||
'. See https://docs.python.org/3/library/time.html#time.strftime for more information.')
|
||||
return cls.add_info('log_time', 'formatted time when logging', 'The time format string'
|
||||
'. See https://docs.python.org/3/library/time.html#time.strftime for more information.')
|
||||
|
||||
def format(self, message: LogMessage) -> str:
|
||||
return f'[{message.log_time}]'
|
||||
|
@ -2,6 +2,9 @@
|
||||
# DR basic running from source
|
||||
# DR build (by nuitka)
|
||||
|
||||
# for function
|
||||
lib-not-dr
|
||||
|
||||
# for images
|
||||
# not for pypy >= 3.10
|
||||
pillow >= 10.0.0; (platform_python_implementation == "PyPy" and python_version < "3.10") or platform_python_implementation == "CPython"
|
||||
|
@ -3,6 +3,9 @@
|
||||
# DR build (by nuitka)
|
||||
# DR contributing
|
||||
|
||||
# for function
|
||||
lib-not-dr
|
||||
|
||||
# for images
|
||||
# not for pypy >= 3.10
|
||||
pillow >= 10.0.0; (platform_python_implementation == "PyPy" and python_version < "3.10") or platform_python_implementation == "CPython"
|
||||
|
@ -1,6 +1,9 @@
|
||||
# this requirement is for
|
||||
# DR basic running from source
|
||||
|
||||
# for function
|
||||
lib-not-dr
|
||||
|
||||
# for images
|
||||
# not for pypy >= 3.10
|
||||
pillow >= 10.0.0; (platform_python_implementation == "PyPy" and python_version < "3.10") or platform_python_implementation == "CPython"
|
||||
|
Loading…
Reference in New Issue
Block a user