| @@ -32,6 +32,7 @@ SynthesiserVoice::SynthesiserVoice() | |||
| currentPlayingMidiChannel (0), | |||
| noteOnTime (0), | |||
| keyIsDown (false), | |||
| sustainPedalDown (false), | |||
| sostenutoPedalDown (false) | |||
| { | |||
| } | |||
| @@ -296,6 +297,7 @@ void Synthesiser::startVoice (SynthesiserVoice* const voice, | |||
| voice->currentlyPlayingSound = sound; | |||
| voice->keyIsDown = true; | |||
| voice->sostenutoPedalDown = false; | |||
| voice->sustainPedalDown = sustainPedalsDown[midiChannel]; | |||
| voice->startNote (midiNoteNumber, velocity, sound, | |||
| lastPitchWheelValues [midiChannel - 1]); | |||
| @@ -331,9 +333,11 @@ void Synthesiser::noteOff (const int midiChannel, | |||
| if (sound->appliesToNote (midiNoteNumber) | |||
| && sound->appliesToChannel (midiChannel)) | |||
| { | |||
| jassert (! voice->keyIsDown || voice->sustainPedalDown == sustainPedalsDown [midiChannel]); | |||
| voice->keyIsDown = false; | |||
| if (! (sustainPedalsDown [midiChannel] || voice->sostenutoPedalDown)) | |||
| if (! (voice->sustainPedalDown || voice->sostenutoPedalDown)) | |||
| stopVoice (voice, velocity, allowTailOff); | |||
| } | |||
| } | |||
| @@ -427,6 +431,14 @@ void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) | |||
| if (isDown) | |||
| { | |||
| sustainPedalsDown.setBit (midiChannel); | |||
| for (int i = voices.size(); --i >= 0;) | |||
| { | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| if (voice->isPlayingChannel (midiChannel) && voice->isKeyDown()) | |||
| voice->sustainPedalDown = true; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| @@ -434,8 +446,13 @@ void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) | |||
| { | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| if (voice->isPlayingChannel (midiChannel) && ! voice->keyIsDown) | |||
| stopVoice (voice, 1.0f, true); | |||
| if (voice->isPlayingChannel (midiChannel)) | |||
| { | |||
| voice->sustainPedalDown = false; | |||
| if (! voice->isKeyDown()) | |||
| stopVoice (voice, 1.0f, true); | |||
| } | |||
| } | |||
| sustainPedalsDown.clearBit (midiChannel); | |||
| @@ -505,8 +522,13 @@ struct VoiceAgeSorter | |||
| SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||
| int /*midiChannel*/, int midiNoteNumber) const | |||
| { | |||
| SynthesiserVoice* bottom = nullptr; | |||
| SynthesiserVoice* top = nullptr; | |||
| // This voice-stealing algorithm applies the following heuristics: | |||
| // - Re-use the oldest notes first | |||
| // - Protect the lowest & topmost notes, even if sustained, but not if they've been released. | |||
| // These are the voices we want to protect (ie: only steal if unavoidable) | |||
| SynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase | |||
| SynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase | |||
| // this is a list of voices we can steal, sorted by how long they've been running | |||
| Array<SynthesiserVoice*> usableVoices; | |||
| @@ -516,24 +538,33 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||
| { | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| jassert (voice->isVoiceActive()); // We wouldn't be here otherwise | |||
| if (voice->canPlaySound (soundToPlay)) | |||
| { | |||
| VoiceAgeSorter sorter; | |||
| usableVoices.addSorted (sorter, voice); | |||
| const int note = voice->getCurrentlyPlayingNote(); | |||
| if (! voice->isPlayingButReleased()) // Don't protect released notes | |||
| { | |||
| const int note = voice->getCurrentlyPlayingNote(); | |||
| if (bottom == nullptr || note < bottom->getCurrentlyPlayingNote()) | |||
| bottom = voice; | |||
| if (low == nullptr || note < low->getCurrentlyPlayingNote()) | |||
| low = voice; | |||
| if (top == nullptr || note > top->getCurrentlyPlayingNote()) | |||
| top = voice; | |||
| if (top == nullptr || note > top->getCurrentlyPlayingNote()) | |||
| top = voice; | |||
| } | |||
| } | |||
| } | |||
| // Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s) | |||
| if (top == low) | |||
| top = nullptr; | |||
| const int numUsableVoices = usableVoices.size(); | |||
| // The oldest note that's playing with the target pitch playing is ideal.. | |||
| // The oldest note that's playing with the target pitch is ideal.. | |||
| for (int i = 0; i < numUsableVoices; ++i) | |||
| { | |||
| SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
| @@ -547,7 +578,7 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||
| { | |||
| SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
| if (voice != bottom && voice != top && ! voice->isKeyDown() && ! voice->isSostenutoPedalDown()) | |||
| if (voice != low && voice != top && voice->isPlayingButReleased()) | |||
| return voice; | |||
| } | |||
| @@ -556,21 +587,25 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||
| { | |||
| SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
| if (voice != bottom && voice != top && ! voice->isKeyDown()) | |||
| if (voice != low && voice != top && ! voice->isKeyDown()) | |||
| return voice; | |||
| } | |||
| // At this point, all notes have fingers on them, so look for the oldest note | |||
| // that isn't the top or bottom note.. | |||
| // Oldest voice that isn't protected | |||
| for (int i = 0; i < numUsableVoices; ++i) | |||
| { | |||
| SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
| if (voice != bottom && voice != top) | |||
| if (voice != low && voice != top) | |||
| return voice; | |||
| } | |||
| // ..otherwise, there's only one or two voices to choose from - prefer to steal the highest one: | |||
| jassert (top != nullptr || bottom != nullptr); | |||
| return (top == nullptr ? bottom : top); | |||
| // We've only got "protected" voices now: lowest note takes priority | |||
| jassert (low != nullptr); | |||
| // Duophonic synth: give priority to the bass note: | |||
| if (top != nullptr) | |||
| return top; | |||
| return low; | |||
| } | |||
| @@ -214,9 +214,18 @@ public: | |||
| */ | |||
| bool isKeyDown() const noexcept { return keyIsDown; } | |||
| /** Returns true if the sustain pedal is currently active for this voice. */ | |||
| bool isSustainPedalDown() const noexcept { return sustainPedalDown; } | |||
| /** Returns true if the sostenuto pedal is currently active for this voice. */ | |||
| bool isSostenutoPedalDown() const noexcept { return sostenutoPedalDown; } | |||
| /** Returns true if a voice is sounding in its release phase **/ | |||
| bool isPlayingButReleased() const noexcept | |||
| { | |||
| return isVoiceActive() && ! (isKeyDown() || isSostenutoPedalDown() || isSustainPedalDown()); | |||
| } | |||
| /** Returns true if this voice started playing its current note before the other voice did. */ | |||
| bool wasStartedBefore (const SynthesiserVoice& other) const noexcept; | |||
| @@ -244,7 +253,7 @@ private: | |||
| int currentlyPlayingNote, currentPlayingMidiChannel; | |||
| uint32 noteOnTime; | |||
| SynthesiserSound::Ptr currentlyPlayingSound; | |||
| bool keyIsDown, sostenutoPedalDown; | |||
| bool keyIsDown, sustainPedalDown, sostenutoPedalDown; | |||
| #if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||
| // Note the new parameters for this method. | |||
| @@ -45,6 +45,14 @@ namespace CoreMidiHelpers | |||
| #define CHECK_ERROR(a) CoreMidiHelpers::checkError (a, __LINE__) | |||
| //============================================================================== | |||
| struct ScopedCFString | |||
| { | |||
| ScopedCFString() noexcept : cfString (nullptr) {} | |||
| ~ScopedCFString() noexcept { if (cfString != nullptr) CFRelease (cfString); } | |||
| CFStringRef cfString; | |||
| }; | |||
| static String getMidiObjectName (MIDIObjectRef entity) | |||
| { | |||
| String result; | |||
| @@ -116,7 +124,7 @@ namespace CoreMidiHelpers | |||
| if (numConnections > 0) | |||
| { | |||
| const SInt32* pid = reinterpret_cast <const SInt32*> (CFDataGetBytePtr (connections)); | |||
| const SInt32* pid = reinterpret_cast<const SInt32*> (CFDataGetBytePtr (connections)); | |||
| for (int i = 0; i < numConnections; ++i, ++pid) | |||
| { | |||
| @@ -133,7 +141,7 @@ namespace CoreMidiHelpers | |||
| || connObjectType == kMIDIObjectType_ExternalDestination) | |||
| { | |||
| // Connected to an external device's endpoint (10.3 and later). | |||
| s = getEndpointName (static_cast <MIDIEndpointRef> (connObject), true); | |||
| s = getEndpointName (static_cast<MIDIEndpointRef> (connObject), true); | |||
| } | |||
| else | |||
| { | |||
| @@ -208,9 +216,9 @@ namespace CoreMidiHelpers | |||
| // correctly when called from the message thread! | |||
| jassert (MessageManager::getInstance()->isThisTheMessageThread()); | |||
| CFStringRef name = getGlobalMidiClientName().toCFString(); | |||
| CHECK_ERROR (MIDIClientCreate (name, &globalSystemChangeCallback, nullptr, &globalMidiClient)); | |||
| CFRelease (name); | |||
| CoreMidiHelpers::ScopedCFString name; | |||
| name.cfString = getGlobalMidiClientName().toCFString(); | |||
| CHECK_ERROR (MIDIClientCreate (name.cfString, &globalSystemChangeCallback, nullptr, &globalMidiClient)); | |||
| } | |||
| return globalMidiClient; | |||
| @@ -220,12 +228,12 @@ namespace CoreMidiHelpers | |||
| class MidiPortAndEndpoint | |||
| { | |||
| public: | |||
| MidiPortAndEndpoint (MIDIPortRef p, MIDIEndpointRef ep) | |||
| MidiPortAndEndpoint (MIDIPortRef p, MIDIEndpointRef ep) noexcept | |||
| : port (p), endPoint (ep) | |||
| { | |||
| } | |||
| ~MidiPortAndEndpoint() | |||
| ~MidiPortAndEndpoint() noexcept | |||
| { | |||
| if (port != 0) | |||
| MIDIPortDispose (port); | |||
| @@ -234,7 +242,7 @@ namespace CoreMidiHelpers | |||
| MIDIEndpointDispose (endPoint); | |||
| } | |||
| void send (const MIDIPacketList* const packets) | |||
| void send (const MIDIPacketList* const packets) noexcept | |||
| { | |||
| if (port != 0) | |||
| MIDISend (port, endPoint, packets); | |||
| @@ -302,7 +310,7 @@ namespace CoreMidiHelpers | |||
| static void midiInputProc (const MIDIPacketList* pktlist, void* readProcRefCon, void* /*srcConnRefCon*/) | |||
| { | |||
| static_cast <MidiPortAndCallback*> (readProcRefCon)->handlePackets (pktlist); | |||
| static_cast<MidiPortAndCallback*> (readProcRefCon)->handlePackets (pktlist); | |||
| } | |||
| } | |||
| @@ -318,19 +326,18 @@ MidiOutput* MidiOutput::openDevice (int index) | |||
| { | |||
| MIDIEndpointRef endPoint = MIDIGetDestination ((ItemCount) index); | |||
| CFStringRef pname; | |||
| if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname))) | |||
| CoreMidiHelpers::ScopedCFString pname; | |||
| if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname.cfString))) | |||
| { | |||
| MIDIClientRef client = CoreMidiHelpers::getGlobalMidiClient(); | |||
| MIDIPortRef port; | |||
| if (client != 0 && CHECK_ERROR (MIDIOutputPortCreate (client, pname, &port))) | |||
| if (client != 0 && CHECK_ERROR (MIDIOutputPortCreate (client, pname.cfString, &port))) | |||
| { | |||
| mo = new MidiOutput(); | |||
| mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (port, endPoint); | |||
| } | |||
| CFRelease (pname); | |||
| } | |||
| } | |||
| @@ -339,20 +346,20 @@ MidiOutput* MidiOutput::openDevice (int index) | |||
| MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | |||
| { | |||
| MidiOutput* mo = nullptr; | |||
| MIDIClientRef client = CoreMidiHelpers::getGlobalMidiClient(); | |||
| MIDIEndpointRef endPoint; | |||
| CFStringRef name = deviceName.toCFString(); | |||
| if (client != 0 && CHECK_ERROR (MIDISourceCreate (client, name, &endPoint))) | |||
| CoreMidiHelpers::ScopedCFString name; | |||
| name.cfString = deviceName.toCFString(); | |||
| if (client != 0 && CHECK_ERROR (MIDISourceCreate (client, name.cfString, &endPoint))) | |||
| { | |||
| mo = new MidiOutput(); | |||
| MidiOutput* mo = new MidiOutput(); | |||
| mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (0, endPoint); | |||
| return mo; | |||
| } | |||
| CFRelease (name); | |||
| return mo; | |||
| return nullptr; | |||
| } | |||
| MidiOutput::~MidiOutput() | |||
| @@ -370,7 +377,7 @@ void MidiOutput::sendMessageNow (const MidiMessage& message) | |||
| const MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); | |||
| #endif | |||
| HeapBlock <MIDIPacketList> allocatedPackets; | |||
| HeapBlock<MIDIPacketList> allocatedPackets; | |||
| MIDIPacketList stackPacket; | |||
| MIDIPacketList* packetToSend = &stackPacket; | |||
| const size_t dataSize = (size_t) message.getRawDataSize(); | |||
| @@ -436,16 +443,16 @@ MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) | |||
| { | |||
| if (MIDIEndpointRef endPoint = MIDIGetSource ((ItemCount) index)) | |||
| { | |||
| CFStringRef name; | |||
| ScopedCFString name; | |||
| if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &name))) | |||
| if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &name.cfString))) | |||
| { | |||
| if (MIDIClientRef client = getGlobalMidiClient()) | |||
| { | |||
| MIDIPortRef port; | |||
| ScopedPointer <MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback)); | |||
| ScopedPointer<MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback)); | |||
| if (CHECK_ERROR (MIDIInputPortCreate (client, name, midiInputProc, mpc, &port))) | |||
| if (CHECK_ERROR (MIDIInputPortCreate (client, name.cfString, midiInputProc, mpc, &port))) | |||
| { | |||
| if (CHECK_ERROR (MIDIPortConnectSource (port, endPoint, nullptr))) | |||
| { | |||
| @@ -465,8 +472,6 @@ MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) | |||
| } | |||
| } | |||
| } | |||
| CFRelease (name); | |||
| } | |||
| } | |||
| @@ -482,13 +487,14 @@ MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallba | |||
| if (MIDIClientRef client = getGlobalMidiClient()) | |||
| { | |||
| ScopedPointer <MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback)); | |||
| ScopedPointer<MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback)); | |||
| mpc->active = false; | |||
| MIDIEndpointRef endPoint; | |||
| CFStringRef name = deviceName.toCFString(); | |||
| ScopedCFString name; | |||
| name.cfString = deviceName.toCFString(); | |||
| if (CHECK_ERROR (MIDIDestinationCreate (client, name, midiInputProc, mpc, &endPoint))) | |||
| if (CHECK_ERROR (MIDIDestinationCreate (client, name.cfString, midiInputProc, mpc, &endPoint))) | |||
| { | |||
| mpc->portAndEndpoint = new MidiPortAndEndpoint (0, endPoint); | |||
| @@ -499,8 +505,6 @@ MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallba | |||
| const ScopedLock sl (callbackLock); | |||
| activeCallbacks.add (mpc.release()); | |||
| } | |||
| CFRelease (name); | |||
| } | |||
| return mi; | |||
| @@ -916,6 +916,11 @@ public: | |||
| const File temp (File::getSpecialLocation (File::tempDirectory)); | |||
| expect (! File::nonexistent.exists()); | |||
| expect (! File::nonexistent.existsAsFile()); | |||
| expect (! File::nonexistent.isDirectory()); | |||
| #if ! JUCE_WINDOWS | |||
| expect (File("/").isDirectory()); | |||
| #endif | |||
| expect (home.isDirectory()); | |||
| expect (home.exists()); | |||
| expect (! home.existsAsFile()); | |||
| @@ -50,7 +50,7 @@ inline void deleteAndZero (Type& pointer) { delete poi | |||
| a specific number of bytes, | |||
| */ | |||
| template <typename Type, typename IntegerType> | |||
| inline Type* addBytesToPointer (Type* pointer, IntegerType bytes) noexcept { return (Type*) (((char*) pointer) + bytes); } | |||
| inline Type* addBytesToPointer (Type* basePointer, IntegerType bytes) noexcept { return (Type*) (((char*) basePointer) + bytes); } | |||
| /** A handy function which returns the difference between any two pointers, in bytes. | |||
| The address of the second pointer is subtracted from the first, and the difference in bytes is returned. | |||
| @@ -62,7 +62,7 @@ inline int getAddressDifference (Type1* pointer1, Type2* pointer2) noexcept { r | |||
| nullptr if the pointer is null. | |||
| */ | |||
| template <class Type> | |||
| inline Type* createCopyIfNotNull (const Type* pointer) { return pointer != nullptr ? new Type (*pointer) : nullptr; } | |||
| inline Type* createCopyIfNotNull (const Type* objectToCopy) { return objectToCopy != nullptr ? new Type (*objectToCopy) : nullptr; } | |||
| //============================================================================== | |||
| #if JUCE_MAC || JUCE_IOS || DOXYGEN | |||
| @@ -265,8 +265,8 @@ bool File::isDirectory() const | |||
| { | |||
| juce_statStruct info; | |||
| return fullPath.isEmpty() | |||
| || (juce_stat (fullPath, info) && ((info.st_mode & S_IFDIR) != 0)); | |||
| return fullPath.isNotEmpty() | |||
| && (juce_stat (fullPath, info) && ((info.st_mode & S_IFDIR) != 0)); | |||
| } | |||
| bool File::exists() const | |||
| @@ -89,12 +89,12 @@ public: | |||
| */ | |||
| String (const char* text, size_t maxChars); | |||
| /** Creates a string from a whcar_t character string. | |||
| /** Creates a string from a wchar_t character string. | |||
| Depending on the platform, this may be treated as either UTF-32 or UTF-16. | |||
| */ | |||
| String (const wchar_t* text); | |||
| /** Creates a string from a whcar_t character string. | |||
| /** Creates a string from a wchar_t character string. | |||
| Depending on the platform, this may be treated as either UTF-32 or UTF-16. | |||
| */ | |||
| String (const wchar_t* text, size_t maxChars); | |||
| @@ -32,8 +32,8 @@ namespace | |||
| jassert ((int) x >= -maxVal && (int) x <= maxVal | |||
| && (int) y >= -maxVal && (int) y <= maxVal | |||
| && (int) w >= -maxVal && (int) w <= maxVal | |||
| && (int) h >= -maxVal && (int) h <= maxVal); | |||
| && (int) w >= 0 && (int) w <= maxVal | |||
| && (int) h >= 0 && (int) h <= maxVal); | |||
| #endif | |||
| return Rectangle<Type> (x, y, w, h); | |||
| @@ -427,6 +427,8 @@ void Graphics::drawRect (const Rectangle<int>& r, int lineThickness) const | |||
| void Graphics::drawRect (Rectangle<float> r, const float lineThickness) const | |||
| { | |||
| jassert (r.getWidth() >= 0.0f && r.getHeight() >= 0.0f); | |||
| RectangleList<float> rects; | |||
| rects.addWithoutMerging (r.removeFromTop (lineThickness)); | |||
| rects.addWithoutMerging (r.removeFromBottom (lineThickness)); | |||
| @@ -1153,12 +1153,15 @@ void Component::setBounds (const int x, const int y, int w, int h) | |||
| void Component::sendMovedResizedMessagesIfPending() | |||
| { | |||
| if (flags.isMoveCallbackPending || flags.isResizeCallbackPending) | |||
| { | |||
| sendMovedResizedMessages (flags.isMoveCallbackPending, flags.isResizeCallbackPending); | |||
| const bool wasMoved = flags.isMoveCallbackPending; | |||
| const bool wasResized = flags.isResizeCallbackPending; | |||
| if (wasMoved || wasResized) | |||
| { | |||
| flags.isMoveCallbackPending = false; | |||
| flags.isResizeCallbackPending = false; | |||
| sendMovedResizedMessages (wasMoved, wasResized); | |||
| } | |||
| } | |||
| @@ -3723,12 +3723,45 @@ void Desktop::Displays::findDisplays (float masterScale) | |||
| { | |||
| DisplayGeometry& geometry = DisplayGeometry::getOrCreateInstance (display, masterScale); | |||
| // add the main display first | |||
| int mainDisplayIdx; | |||
| for (mainDisplayIdx = 0; mainDisplayIdx < geometry.infos.size(); ++mainDisplayIdx) | |||
| { | |||
| const DisplayGeometry::ExtendedInfo& info = geometry.infos.getReference (mainDisplayIdx); | |||
| if (info.isMain) | |||
| break; | |||
| } | |||
| // no main display found then use the first | |||
| if (mainDisplayIdx >= geometry.infos.size()) | |||
| mainDisplayIdx = 0; | |||
| // add the main display | |||
| { | |||
| const DisplayGeometry::ExtendedInfo& info = | |||
| geometry.infos.getReference (mainDisplayIdx); | |||
| Desktop::Displays::Display d; | |||
| d.isMain = true; | |||
| d.scale = masterScale * info.scale; | |||
| d.dpi = info.dpi; | |||
| d.totalArea = DisplayGeometry::physicalToScaled (info.totalBounds); | |||
| d.userArea = (info.usableBounds / d.scale) + info.topLeftScaled; | |||
| displays.add (d); | |||
| } | |||
| for (int i = 0; i < geometry.infos.size(); ++i) | |||
| { | |||
| // don't add the main display a second time | |||
| if (i == mainDisplayIdx) | |||
| continue; | |||
| const DisplayGeometry::ExtendedInfo& info = geometry.infos.getReference (i); | |||
| Desktop::Displays::Display d; | |||
| d.isMain = info.isMain; | |||
| d.isMain = false; | |||
| d.scale = masterScale * info.scale; | |||
| d.dpi = info.dpi; | |||
| @@ -188,6 +188,8 @@ public: | |||
| context.currentRenderScale = scale; | |||
| context.renderer->renderOpenGL(); | |||
| clearGLError(); | |||
| bindVertexArray(); | |||
| } | |||
| if (context.renderComponents) | |||
| @@ -231,6 +233,14 @@ public: | |||
| } | |||
| } | |||
| void bindVertexArray() noexcept | |||
| { | |||
| #if JUCE_OPENGL3 | |||
| if (vertexArrayObject != 0) | |||
| glBindVertexArray (vertexArrayObject); | |||
| #endif | |||
| } | |||
| void checkViewportBounds() | |||
| { | |||
| const Rectangle<int> screenBounds (component.getTopLevelComponent()->getScreenBounds()); | |||
| @@ -288,11 +298,7 @@ public: | |||
| context.extensions.glActiveTexture (GL_TEXTURE0); | |||
| glBindTexture (GL_TEXTURE_2D, cachedImageFrameBuffer.getTextureID()); | |||
| #if JUCE_OPENGL3 | |||
| if (vertexArrayObject != 0) | |||
| glBindVertexArray (vertexArrayObject); | |||
| #endif | |||
| bindVertexArray(); | |||
| const Rectangle<int> cacheBounds (cachedImageFrameBuffer.getWidth(), cachedImageFrameBuffer.getHeight()); | |||
| context.copyTexture (cacheBounds, cacheBounds, cacheBounds.getWidth(), cacheBounds.getHeight(), false); | |||
| @@ -401,7 +407,7 @@ public: | |||
| if (OpenGLShaderProgram::getLanguageVersion() > 1.2) | |||
| { | |||
| glGenVertexArrays (1, &vertexArrayObject); | |||
| glBindVertexArray (vertexArrayObject); | |||
| bindVertexArray(); | |||
| } | |||
| #endif | |||