diff options
| -rw-r--r-- | common/Android.mk | 3 | ||||
| -rw-r--r-- | common/java/com/android/common/OperationScheduler.java | 297 | ||||
| -rw-r--r-- | common/tests/src/com/android/common/OperationSchedulerTest.java | 116 | ||||
| -rw-r--r-- | common/tests/src/com/android/common/PatternsTest.java | 28 | ||||
| -rw-r--r-- | libs/audioflinger/AudioFlinger.cpp | 237 | ||||
| -rw-r--r-- | libs/audioflinger/AudioFlinger.h | 22 | ||||
| -rw-r--r-- | libs/surfaceflinger/LayerBlur.cpp | 1 | ||||
| -rw-r--r-- | libs/surfaceflinger/LayerBuffer.cpp | 7 | ||||
| -rw-r--r-- | libs/utils/ResourceTypes.cpp | 20 | ||||
| -rw-r--r-- | opengl/libagl/array.cpp | 36 |
10 files changed, 636 insertions, 131 deletions
diff --git a/common/Android.mk b/common/Android.mk index 349bb86751..76091ebf7f 100644 --- a/common/Android.mk +++ b/common/Android.mk @@ -24,3 +24,6 @@ include $(BUILD_STATIC_JAVA_LIBRARY) # Include this library in the build server's output directory $(call dist-for-goals, droid, $(LOCAL_BUILT_MODULE):android-common.jar) + +# Build the test package +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/common/java/com/android/common/OperationScheduler.java b/common/java/com/android/common/OperationScheduler.java new file mode 100644 index 0000000000..71b22ce63e --- /dev/null +++ b/common/java/com/android/common/OperationScheduler.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.common; + +import android.content.SharedPreferences; +import android.text.format.Time; + +import java.util.Map; +import java.util.TreeSet; + +/** + * Tracks the success/failure history of a particular network operation in + * persistent storage and computes retry strategy accordingly. Handles + * exponential backoff, periodic rescheduling, event-driven triggering, + * retry-after moratorium intervals, etc. based on caller-specified parameters. + * + * <p>This class does not directly perform or invoke any operations, + * it only keeps track of the schedule. Somebody else needs to call + * {@link #getNextTimeMillis()} as appropriate and do the actual work. + */ +public class OperationScheduler { + /** Tunable parameter options for {@link #getNextTimeMillis}. */ + public static class Options { + /** Wait this long after every error before retrying. */ + public long backoffFixedMillis = 0; + + /** Wait this long times the number of consecutive errors so far before retrying. */ + public long backoffIncrementalMillis = 5000; + + /** Maximum duration of moratorium to honor. Mostly an issue for clock rollbacks. */ + public long maxMoratoriumMillis = 24 * 3600 * 1000; + + /** Minimum duration after success to wait before allowing another trigger. */ + public long minTriggerMillis = 0; + + /** Automatically trigger this long after the last success. */ + public long periodicIntervalMillis = 0; + + @Override + public String toString() { + return String.format( + "OperationScheduler.Options[backoff=%.1f+%.1f max=%.1f min=%.1f period=%.1f]", + backoffFixedMillis / 1000.0, backoffIncrementalMillis / 1000.0, + maxMoratoriumMillis / 1000.0, minTriggerMillis / 1000.0, + periodicIntervalMillis / 1000.0); + } + } + + private static final String PREFIX = "OperationScheduler_"; + private final SharedPreferences mStorage; + + /** + * Initialize the scheduler state. + * @param storage to use for recording the state of operations across restarts/reboots + */ + public OperationScheduler(SharedPreferences storage) { + mStorage = storage; + } + + /** + * Parse scheduler options supplied in this string form: + * + * <pre> + * backoff=(fixed)+(incremental) max=(maxmoratorium) min=(mintrigger) [period=](interval) + * </pre> + * + * All values are times in (possibly fractional) <em>seconds</em> (not milliseconds). + * Omitted settings are left at whatever existing default value was passed in. + * + * <p> + * The default options: <code>backoff=0+5 max=86400 min=0 period=0</code><br> + * Fractions are OK: <code>backoff=+2.5 period=10.0</code><br> + * The "period=" can be omitted: <code>3600</code><br> + * + * @param spec describing some or all scheduler options. + * @param options to update with parsed values. + * @return the options passed in (for convenience) + * @throws IllegalArgumentException if the syntax is invalid + */ + public static Options parseOptions(String spec, Options options) + throws IllegalArgumentException { + for (String param : spec.split(" +")) { + if (param.length() == 0) continue; + if (param.startsWith("backoff=")) { + int plus = param.indexOf('+', 8); + if (plus < 0) { + options.backoffFixedMillis = parseSeconds(param.substring(8)); + } else { + if (plus > 8) { + options.backoffFixedMillis = parseSeconds(param.substring(8, plus)); + } + options.backoffIncrementalMillis = parseSeconds(param.substring(plus + 1)); + } + } else if (param.startsWith("max=")) { + options.maxMoratoriumMillis = parseSeconds(param.substring(4)); + } else if (param.startsWith("min=")) { + options.minTriggerMillis = parseSeconds(param.substring(4)); + } else if (param.startsWith("period=")) { + options.periodicIntervalMillis = parseSeconds(param.substring(7)); + } else { + options.periodicIntervalMillis = parseSeconds(param); + } + } + return options; + } + + private static long parseSeconds(String param) throws NumberFormatException { + return (long) (Float.parseFloat(param) * 1000); + } + + /** + * Compute the time of the next operation. Does not modify any state. + * + * @param options to use for this computation. + * @return the wall clock time ({@link System#currentTimeMillis()}) when the + * next operation should be attempted -- immediately, if the return value is + * before the current time. + */ + public long getNextTimeMillis(Options options) { + boolean enabledState = mStorage.getBoolean(PREFIX + "enabledState", true); + if (!enabledState) return Long.MAX_VALUE; + + boolean permanentError = mStorage.getBoolean(PREFIX + "permanentError", false); + if (permanentError) return Long.MAX_VALUE; + + // We do quite a bit of limiting to prevent a clock rollback from totally + // hosing the scheduler. Times which are supposed to be in the past are + // clipped to the current time so we don't languish forever. + + int errorCount = mStorage.getInt(PREFIX + "errorCount", 0); + long now = System.currentTimeMillis(); + long lastSuccessTimeMillis = getTimeBefore(PREFIX + "lastSuccessTimeMillis", now); + long lastErrorTimeMillis = getTimeBefore(PREFIX + "lastErrorTimeMillis", now); + long triggerTimeMillis = mStorage.getLong(PREFIX + "triggerTimeMillis", Long.MAX_VALUE); + long moratoriumSetMillis = mStorage.getLong(PREFIX + "moratoriumSetTimeMillis", 0); + long moratoriumTimeMillis = getTimeBefore(PREFIX + "moratoriumTimeMillis", + moratoriumSetMillis + options.maxMoratoriumMillis); + + long time = triggerTimeMillis; + if (options.periodicIntervalMillis > 0) { + time = Math.min(time, lastSuccessTimeMillis + options.periodicIntervalMillis); + } + if (time >= moratoriumTimeMillis - options.maxMoratoriumMillis) { + time = Math.max(time, moratoriumTimeMillis); + } + time = Math.max(time, lastSuccessTimeMillis + options.minTriggerMillis); + time = Math.max(time, lastErrorTimeMillis + options.backoffFixedMillis + + options.backoffIncrementalMillis * errorCount); + return time; + } + + /** + * Fetch a {@link SharedPreferences} property, but force it to be before + * a certain time, updating the value if necessary. This is to recover + * gracefully from clock rollbacks which could otherwise strand our timers. + * + * @param name of SharedPreferences key + * @param max time to allow in result + * @return current value attached to key (default 0), limited by max + */ + private long getTimeBefore(String name, long max) { + long time = mStorage.getLong(name, 0); + if (time > max) mStorage.edit().putLong(name, (time = max)).commit(); + return time; + } + + /** + * Request an operation to be performed at a certain time. The actual + * scheduled time may be affected by error backoff logic and defined + * minimum intervals. + * + * @param millis wall clock time ({@link System#currentTimeMillis()}) to + * trigger another operation; 0 to trigger immediately + */ + public void setTriggerTimeMillis(long millis) { + mStorage.edit().putLong(PREFIX + "triggerTimeMillis", millis).commit(); + } + + /** + * Forbid any operations until after a certain (absolute) time. + * Commonly used when a server returns a "Retry-After:" type directive. + * Limited by {@link #Options.maxMoratoriumMillis}. + * + * @param millis wall clock time ({@link System#currentTimeMillis()}) to + * wait before attempting any more operations; 0 to remove moratorium + */ + public void setMoratoriumTimeMillis(long millis) { + mStorage.edit() + .putLong(PREFIX + "moratoriumTimeMillis", millis) + .putLong(PREFIX + "moratoriumSetTimeMillis", System.currentTimeMillis()) + .commit(); + } + + /** + * Enable or disable all operations. When disabled, all calls to + * {@link #getNextTimeMillis()} return {@link Long#MAX_VALUE}. + * Commonly used when data network availability goes up and down. + * + * @param enabled if operations can be performed + */ + public void setEnabledState(boolean enabled) { + mStorage.edit().putBoolean(PREFIX + "enabledState", enabled).commit(); + } + + /** + * Report successful completion of an operation. Resets all error + * counters, clears any trigger directives, and records the success. + */ + public void onSuccess() { + resetTransientError(); + resetPermanentError(); + long now = System.currentTimeMillis(); + mStorage.edit() + .remove(PREFIX + "errorCount") + .remove(PREFIX + "lastErrorTimeMillis") + .remove(PREFIX + "permanentError") + .remove(PREFIX + "triggerTimeMillis") + .putLong(PREFIX + "lastSuccessTimeMillis", now).commit(); + } + + /** + * Report a transient error (usually a network failure). Increments + * the error count and records the time of the latest error for backoff + * purposes. + */ + public void onTransientError() { + long now = System.currentTimeMillis(); + mStorage.edit().putLong(PREFIX + "lastErrorTimeMillis", now).commit(); + mStorage.edit().putInt(PREFIX + "errorCount", + mStorage.getInt(PREFIX + "errorCount", 0) + 1).commit(); + } + + /** + * Reset all transient error counts, allowing the next operation to proceed + * immediately without backoff. Commonly used on network state changes, when + * partial progress occurs (some data received), and in other circumstances + * where there is reason to hope things might start working better. + */ + public void resetTransientError() { + mStorage.edit() + .remove(PREFIX + "lastErrorTimeMillis") + .remove(PREFIX + "errorCount").commit(); + } + + /** + * Report a permanent error that will not go away until further notice. + * No operation will be scheduled until {@link #resetPermanentError()} + * is called. Commonly used for authentication failures (which are reset + * when the accounts database is updated). + */ + public void onPermanentError() { + mStorage.edit().putBoolean(PREFIX + "permanentError", true).commit(); + } + + /** + * Reset any permanent error status set by {@link #onPermanentError}, + * allowing operations to be scheduled as normal. + */ + public void resetPermanentError() { + mStorage.edit().remove(PREFIX + "permanentError").commit(); + } + + /** + * Return a string description of the scheduler state for debugging. + */ + public String toString() { + StringBuilder out = new StringBuilder("[OperationScheduler:"); + for (String key : new TreeSet<String>(mStorage.getAll().keySet())) { // Sort keys + if (key.startsWith(PREFIX)) { + if (key.endsWith("TimeMillis")) { + Time time = new Time(); + time.set(mStorage.getLong(key, 0)); + out.append(" ").append(key.substring(PREFIX.length(), key.length() - 10)); + out.append("=").append(time.format("%Y-%m-%d/%H:%M:%S")); + } else { + out.append(" ").append(key.substring(PREFIX.length())); + out.append("=").append(mStorage.getAll().get(key).toString()); + } + } + } + return out.append("]").toString(); + } +} diff --git a/common/tests/src/com/android/common/OperationSchedulerTest.java b/common/tests/src/com/android/common/OperationSchedulerTest.java new file mode 100644 index 0000000000..13f710d13f --- /dev/null +++ b/common/tests/src/com/android/common/OperationSchedulerTest.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.common; + +import android.content.SharedPreferences; +import android.test.AndroidTestCase; + +public class OperationSchedulerTest extends AndroidTestCase { + public void testScheduler() throws Exception { + String name = "OperationSchedulerTest.testScheduler"; + SharedPreferences storage = getContext().getSharedPreferences(name, 0); + storage.edit().clear().commit(); + + OperationScheduler scheduler = new OperationScheduler(storage); + OperationScheduler.Options options = new OperationScheduler.Options(); + assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options)); + + long beforeTrigger = System.currentTimeMillis(); + scheduler.setTriggerTimeMillis(beforeTrigger + 1000000); + assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options)); + + // It will schedule for the later of the trigger and the moratorium... + scheduler.setMoratoriumTimeMillis(beforeTrigger + 500000); + assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options)); + scheduler.setMoratoriumTimeMillis(beforeTrigger + 1500000); + assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); + + // Test enable/disable toggle + scheduler.setEnabledState(false); + assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options)); + scheduler.setEnabledState(true); + assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); + + // Backoff interval after an error + long beforeError = System.currentTimeMillis(); + scheduler.onTransientError(); + long afterError = System.currentTimeMillis(); + assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); + options.backoffFixedMillis = 1000000; + options.backoffIncrementalMillis = 500000; + assertTrue(beforeError + 1500000 <= scheduler.getNextTimeMillis(options)); + assertTrue(afterError + 1500000 >= scheduler.getNextTimeMillis(options)); + + // Two errors: backoff interval increases + beforeError = System.currentTimeMillis(); + scheduler.onTransientError(); + afterError = System.currentTimeMillis(); + assertTrue(beforeError + 2000000 <= scheduler.getNextTimeMillis(options)); + assertTrue(afterError + 2000000 >= scheduler.getNextTimeMillis(options)); + + // Permanent error holds true even if transient errors are reset + // However, we remember that the transient error was reset... + scheduler.onPermanentError(); + assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options)); + scheduler.resetTransientError(); + assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options)); + scheduler.resetPermanentError(); + assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); + + // Success resets the trigger + long beforeSuccess = System.currentTimeMillis(); + scheduler.onSuccess(); + long afterSuccess = System.currentTimeMillis(); + assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options)); + + // The moratorium is not reset by success! + scheduler.setTriggerTimeMillis(beforeSuccess + 500000); + assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); + scheduler.setMoratoriumTimeMillis(0); + assertEquals(beforeSuccess + 500000, scheduler.getNextTimeMillis(options)); + + // Periodic interval after success + options.periodicIntervalMillis = 250000; + assertTrue(beforeSuccess + 250000 <= scheduler.getNextTimeMillis(options)); + assertTrue(afterSuccess + 250000 >= scheduler.getNextTimeMillis(options)); + + // Trigger minimum is also since the last success + options.minTriggerMillis = 1000000; + assertTrue(beforeSuccess + 1000000 <= scheduler.getNextTimeMillis(options)); + assertTrue(afterSuccess + 1000000 >= scheduler.getNextTimeMillis(options)); + } + + public void testParseOptions() throws Exception { + OperationScheduler.Options options = new OperationScheduler.Options(); + assertEquals( + "OperationScheduler.Options[backoff=0.0+5.0 max=86400.0 min=0.0 period=3600.0]", + OperationScheduler.parseOptions("3600", options).toString()); + + assertEquals( + "OperationScheduler.Options[backoff=0.0+2.5 max=86400.0 min=0.0 period=3700.0]", + OperationScheduler.parseOptions("backoff=+2.5 3700", options).toString()); + + assertEquals( + "OperationScheduler.Options[backoff=10.0+2.5 max=12345.6 min=7.0 period=3800.0]", + OperationScheduler.parseOptions("max=12345.6 min=7 backoff=10 period=3800", + options).toString()); + + assertEquals( + "OperationScheduler.Options[backoff=10.0+2.5 max=12345.6 min=7.0 period=3800.0]", + OperationScheduler.parseOptions("", options).toString()); + } +} diff --git a/common/tests/src/com/android/common/PatternsTest.java b/common/tests/src/com/android/common/PatternsTest.java index a89ad62901..7fabe5e275 100644 --- a/common/tests/src/com/android/common/PatternsTest.java +++ b/common/tests/src/com/android/common/PatternsTest.java @@ -28,10 +28,10 @@ public class PatternsTest extends TestCase { public void testTldPattern() throws Exception { boolean t; - t = Patterns.TOP_LEVEL_DOMAIN_PATTERN.matcher("com").matches(); + t = Patterns.TOP_LEVEL_DOMAIN.matcher("com").matches(); assertTrue("Missed valid TLD", t); - t = Patterns.TOP_LEVEL_DOMAIN_PATTERN.matcher("xer").matches(); + t = Patterns.TOP_LEVEL_DOMAIN.matcher("xer").matches(); assertFalse("Matched invalid TLD!", t); } @@ -39,19 +39,19 @@ public class PatternsTest extends TestCase { public void testUrlPattern() throws Exception { boolean t; - t = Patterns.WEB_URL_PATTERN.matcher("http://www.google.com").matches(); + t = Patterns.WEB_URL.matcher("http://www.google.com").matches(); assertTrue("Valid URL", t); - t = Patterns.WEB_URL_PATTERN.matcher("ftp://www.example.com").matches(); + t = Patterns.WEB_URL.matcher("ftp://www.example.com").matches(); assertFalse("Matched invalid protocol", t); - t = Patterns.WEB_URL_PATTERN.matcher("http://www.example.com:8080").matches(); + t = Patterns.WEB_URL.matcher("http://www.example.com:8080").matches(); assertTrue("Didn't match valid URL with port", t); - t = Patterns.WEB_URL_PATTERN.matcher("http://www.example.com:8080/?foo=bar").matches(); + t = Patterns.WEB_URL.matcher("http://www.example.com:8080/?foo=bar").matches(); assertTrue("Didn't match valid URL with port and query args", t); - t = Patterns.WEB_URL_PATTERN.matcher("http://www.example.com:8080/~user/?foo=bar").matches(); + t = Patterns.WEB_URL.matcher("http://www.example.com:8080/~user/?foo=bar").matches(); assertTrue("Didn't match valid URL with ~", t); } @@ -59,10 +59,10 @@ public class PatternsTest extends TestCase { public void testIpPattern() throws Exception { boolean t; - t = Patterns.IP_ADDRESS_PATTERN.matcher("172.29.86.3").matches(); + t = Patterns.IP_ADDRESS.matcher("172.29.86.3").matches(); assertTrue("Valid IP", t); - t = Patterns.IP_ADDRESS_PATTERN.matcher("1234.4321.9.9").matches(); + t = Patterns.IP_ADDRESS.matcher("1234.4321.9.9").matches(); assertFalse("Invalid IP", t); } @@ -70,10 +70,10 @@ public class PatternsTest extends TestCase { public void testDomainPattern() throws Exception { boolean t; - t = Patterns.DOMAIN_NAME_PATTERN.matcher("mail.example.com").matches(); + t = Patterns.DOMAIN_NAME.matcher("mail.example.com").matches(); assertTrue("Valid domain", t); - t = Patterns.DOMAIN_NAME_PATTERN.matcher("__+&42.xer").matches(); + t = Patterns.DOMAIN_NAME.matcher("__+&42.xer").matches(); assertFalse("Invalid domain", t); } @@ -81,10 +81,10 @@ public class PatternsTest extends TestCase { public void testPhonePattern() throws Exception { boolean t; - t = Patterns.PHONE_PATTERN.matcher("(919) 555-1212").matches(); + t = Patterns.PHONE.matcher("(919) 555-1212").matches(); assertTrue("Valid phone", t); - t = Patterns.PHONE_PATTERN.matcher("2334 9323/54321").matches(); + t = Patterns.PHONE.matcher("2334 9323/54321").matches(); assertFalse("Invalid phone", t); String[] tests = { @@ -115,7 +115,7 @@ public class PatternsTest extends TestCase { }; for (String test : tests) { - Matcher m = Patterns.PHONE_PATTERN.matcher(test); + Matcher m = Patterns.PHONE.matcher(test); assertTrue("Valid phone " + test, m.find()); } diff --git a/libs/audioflinger/AudioFlinger.cpp b/libs/audioflinger/AudioFlinger.cpp index d918998e93..daec6e8853 100644 --- a/libs/audioflinger/AudioFlinger.cpp +++ b/libs/audioflinger/AudioFlinger.cpp @@ -674,32 +674,13 @@ void AudioFlinger::binderDied(const wp<IBinder>& who) { } // audioConfigChanged_l() must be called with AudioFlinger::mLock held -void AudioFlinger::audioConfigChanged_l(int event, const sp<ThreadBase>& thread, void *param2) { - int ioHandle = 0; - - for (size_t i = 0; i < mPlaybackThreads.size(); i++) { - if (mPlaybackThreads.valueAt(i) == thread) { - ioHandle = mPlaybackThreads.keyAt(i); - break; - } - } - if (ioHandle == 0) { - for (size_t i = 0; i < mRecordThreads.size(); i++) { - if (mRecordThreads.valueAt(i) == thread) { - ioHandle = mRecordThreads.keyAt(i); - break; - } - } - } - - if (ioHandle != 0) { - size_t size = mNotificationClients.size(); - for (size_t i = 0; i < size; i++) { - sp<IBinder> binder = mNotificationClients.itemAt(i); - LOGV("audioConfigChanged_l() Notifying change to client %p", binder.get()); - sp<IAudioFlingerClient> client = interface_cast<IAudioFlingerClient> (binder); - client->ioConfigChanged(event, ioHandle, param2); - } +void AudioFlinger::audioConfigChanged_l(int event, int ioHandle, void *param2) { + size_t size = mNotificationClients.size(); + for (size_t i = 0; i < size; i++) { + sp<IBinder> binder = mNotificationClients.itemAt(i); + LOGV("audioConfigChanged_l() Notifying change to client %p", binder.get()); + sp<IAudioFlingerClient> client = interface_cast<IAudioFlingerClient> (binder); + client->ioConfigChanged(event, ioHandle, param2); } } @@ -712,10 +693,10 @@ void AudioFlinger::removeClient_l(pid_t pid) // ---------------------------------------------------------------------------- -AudioFlinger::ThreadBase::ThreadBase(const sp<AudioFlinger>& audioFlinger) +AudioFlinger::ThreadBase::ThreadBase(const sp<AudioFlinger>& audioFlinger, int id) : Thread(false), mAudioFlinger(audioFlinger), mSampleRate(0), mFrameCount(0), mChannelCount(0), - mFormat(0), mFrameSize(1), mStandby(false) + mFormat(0), mFrameSize(1), mStandby(false), mId(id), mExiting(false) { } @@ -734,6 +715,7 @@ void AudioFlinger::ThreadBase::exit() LOGV("ThreadBase::exit"); { AutoMutex lock(&mLock); + mExiting = true; requestExit(); mWaitWorkCV.signal(); } @@ -870,8 +852,8 @@ status_t AudioFlinger::ThreadBase::dumpBase(int fd, const Vector<String16>& args // ---------------------------------------------------------------------------- -AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output) - : ThreadBase(audioFlinger), +AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id) + : ThreadBase(audioFlinger, id), mMixBuffer(0), mSuspended(0), mBytesWritten(0), mOutput(output), mLastWriteTime(0), mNumWrites(0), mNumDelayedWrites(0), mInWrite(false) { @@ -1106,15 +1088,6 @@ status_t AudioFlinger::PlaybackThread::addTrack_l(const sp<Track>& track) { status_t status = ALREADY_EXISTS; - // here the track could be either new, or restarted - // in both cases "unstop" the track - if (track->isPaused()) { - track->mState = TrackBase::RESUMING; - LOGV("PAUSED => RESUMING (%d) on thread %p", track->name(), this); - } else { - track->mState = TrackBase::ACTIVE; - LOGV("? => ACTIVE (%d) on thread %p", track->name(), this); - } // set retry count for buffer fill track->mRetryCount = kMaxTrackStartupRetries; if (mActiveTracks.indexOf(track) < 0) { @@ -1173,7 +1146,7 @@ void AudioFlinger::PlaybackThread::audioConfigChanged(int event, int param) { break; } Mutex::Autolock _l(mAudioFlinger->mLock); - mAudioFlinger->audioConfigChanged_l(event, this, param2); + mAudioFlinger->audioConfigChanged_l(event, mId, param2); } void AudioFlinger::PlaybackThread::readOutputParameters() @@ -1194,8 +1167,8 @@ void AudioFlinger::PlaybackThread::readOutputParameters() // ---------------------------------------------------------------------------- -AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output) - : PlaybackThread(audioFlinger, output), +AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id) + : PlaybackThread(audioFlinger, output, id), mAudioMixer(0) { mType = PlaybackThread::MIXER; @@ -1303,7 +1276,6 @@ bool AudioFlinger::MixerThread::threadLoop() } } else if (mBytesWritten != 0 || (mixerStatus == MIXER_TRACKS_ENABLED && longStandbyExit)) { - LOGV("NO DATA READY, %p", this); memset (curBuf, 0, mixBufferSize); sleepTime = 0; LOGV_IF((mBytesWritten == 0 && (mixerStatus == MIXER_TRACKS_ENABLED && longStandbyExit)), "anticipated start"); @@ -1639,8 +1611,8 @@ uint32_t AudioFlinger::MixerThread::idleSleepTimeUs() } // ---------------------------------------------------------------------------- -AudioFlinger::DirectOutputThread::DirectOutputThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output) - : PlaybackThread(audioFlinger, output), +AudioFlinger::DirectOutputThread::DirectOutputThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id) + : PlaybackThread(audioFlinger, output, id), mLeftVolume (1.0), mRightVolume(1.0) { mType = PlaybackThread::DIRECT; @@ -1941,8 +1913,8 @@ uint32_t AudioFlinger::DirectOutputThread::idleSleepTimeUs() // ---------------------------------------------------------------------------- -AudioFlinger::DuplicatingThread::DuplicatingThread(const sp<AudioFlinger>& audioFlinger, AudioFlinger::MixerThread* mainThread) - : MixerThread(audioFlinger, mainThread->getOutput()) +AudioFlinger::DuplicatingThread::DuplicatingThread(const sp<AudioFlinger>& audioFlinger, AudioFlinger::MixerThread* mainThread, int id) + : MixerThread(audioFlinger, mainThread->getOutput(), id) { mType = PlaybackThread::DUPLICATING; addOutputTrack(mainThread); @@ -2305,8 +2277,10 @@ AudioFlinger::PlaybackThread::Track::~Track() LOGV("PlaybackThread::Track destructor"); sp<ThreadBase> thread = mThread.promote(); if (thread != 0) { - Mutex::Autolock _l(thread->mLock); + thread->mLock.lock(); mState = TERMINATED; + thread->mLock.unlock(); + AudioSystem::releaseOutput(thread->id()); } } @@ -2324,6 +2298,9 @@ void AudioFlinger::PlaybackThread::Track::destroy() { // scope for mLock sp<ThreadBase> thread = mThread.promote(); if (thread != 0) { + if (!isOutputTrack() && (mState == ACTIVE || mState == RESUMING)) { + AudioSystem::stopOutput(thread->id(), (AudioSystem::stream_type)mStreamType); + } Mutex::Autolock _l(thread->mLock); PlaybackThread *playbackThread = (PlaybackThread *)thread.get(); playbackThread->destroyTrack_l(this); @@ -2405,14 +2382,37 @@ bool AudioFlinger::PlaybackThread::Track::isReady() const { status_t AudioFlinger::PlaybackThread::Track::start() { + status_t status = NO_ERROR; LOGV("start(%d), calling thread %d", mName, IPCThreadState::self()->getCallingPid()); sp<ThreadBase> thread = mThread.promote(); if (thread != 0) { Mutex::Autolock _l(thread->mLock); - PlaybackThread *playbackThread = (PlaybackThread *)thread.get(); - playbackThread->addTrack_l(this); + int state = mState; + // here the track could be either new, or restarted + // in both cases "unstop" the track + if (mState == PAUSED) { + mState = TrackBase::RESUMING; + LOGV("PAUSED => RESUMING (%d) on thread %p", mName, this); + } else { + mState = TrackBase::ACTIVE; + LOGV("? => ACTIVE (%d) on thread %p", mName, this); + } + + if (!isOutputTrack() && state != ACTIVE && state != RESUMING) { + thread->mLock.unlock(); + status = AudioSystem::startOutput(thread->id(), (AudioSystem::stream_type)mStreamType); + thread->mLock.lock(); + } + if (status == NO_ERROR) { + PlaybackThread *playbackThread = (PlaybackThread *)thread.get(); + playbackThread->addTrack_l(this); + } else { + mState = state; + } + } else { + status = BAD_VALUE; } - return NO_ERROR; + return status; } void AudioFlinger::PlaybackThread::Track::stop() @@ -2421,6 +2421,7 @@ void AudioFlinger::PlaybackThread::Track::stop() sp<ThreadBase> thread = mThread.promote(); if (thread != 0) { Mutex::Autolock _l(thread->mLock); + int state = mState; if (mState > STOPPED) { mState = STOPPED; // If the track is not active (PAUSED and buffers full), flush buffers @@ -2430,6 +2431,11 @@ void AudioFlinger::PlaybackThread::Track::stop() } LOGV("(> STOPPED) => STOPPED (%d) on thread %p", mName, playbackThread); } + if (!isOutputTrack() && (state == ACTIVE || state == RESUMING)) { + thread->mLock.unlock(); + AudioSystem::stopOutput(thread->id(), (AudioSystem::stream_type)mStreamType); + thread->mLock.lock(); + } } } @@ -2442,6 +2448,11 @@ void AudioFlinger::PlaybackThread::Track::pause() if (mState == ACTIVE || mState == RESUMING) { mState = PAUSING; LOGV("ACTIVE/RESUMING => PAUSING (%d) on thread %p", mName, thread.get()); + if (!isOutputTrack()) { + thread->mLock.unlock(); + AudioSystem::stopOutput(thread->id(), (AudioSystem::stream_type)mStreamType); + thread->mLock.lock(); + } } } } @@ -2525,6 +2536,10 @@ AudioFlinger::RecordThread::RecordTrack::RecordTrack( AudioFlinger::RecordThread::RecordTrack::~RecordTrack() { + sp<ThreadBase> thread = mThread.promote(); + if (thread != 0) { + AudioSystem::releaseInput(thread->id()); + } } status_t AudioFlinger::RecordThread::RecordTrack::getNextBuffer(AudioBufferProvider::Buffer* buffer) @@ -2572,8 +2587,9 @@ status_t AudioFlinger::RecordThread::RecordTrack::start() if (thread != 0) { RecordThread *recordThread = (RecordThread *)thread.get(); return recordThread->start(this); + } else { + return BAD_VALUE; } - return NO_INIT; } void AudioFlinger::RecordThread::RecordTrack::stop() @@ -3010,8 +3026,8 @@ status_t AudioFlinger::RecordHandle::onTransact( // ---------------------------------------------------------------------------- -AudioFlinger::RecordThread::RecordThread(const sp<AudioFlinger>& audioFlinger, AudioStreamIn *input, uint32_t sampleRate, uint32_t channels) : - ThreadBase(audioFlinger), +AudioFlinger::RecordThread::RecordThread(const sp<AudioFlinger>& audioFlinger, AudioStreamIn *input, uint32_t sampleRate, uint32_t channels, int id) : + ThreadBase(audioFlinger, id), mInput(input), mResampler(0), mRsmpOutBuffer(0), mRsmpInBuffer(0) { mReqChannelCount = AudioSystem::popCount(channels); @@ -3039,6 +3055,7 @@ void AudioFlinger::RecordThread::onFirstRef() run(buffer, PRIORITY_URGENT_AUDIO); } + bool AudioFlinger::RecordThread::threadLoop() { AudioBufferProvider::Buffer buffer; @@ -3084,6 +3101,10 @@ bool AudioFlinger::RecordThread::threadLoop() } if (mActiveTrack != 0) { + if (mActiveTrack->mState != TrackBase::ACTIVE) { + usleep(5000); + continue; + } buffer.frameCount = mFrameCount; if (LIKELY(mActiveTrack->getNextBuffer(&buffer) == NO_ERROR)) { size_t framesOut = buffer.frameCount; @@ -3181,6 +3202,8 @@ bool AudioFlinger::RecordThread::threadLoop() } mActiveTrack.clear(); + mStartStopCond.broadcast(); + LOGV("RecordThread %p exiting", this); return false; } @@ -3188,37 +3211,71 @@ bool AudioFlinger::RecordThread::threadLoop() status_t AudioFlinger::RecordThread::start(RecordThread::RecordTrack* recordTrack) { LOGV("RecordThread::start"); - AutoMutex lock(&mLock); - - if (mActiveTrack != 0) { - if (recordTrack != mActiveTrack.get()) return -EBUSY; - - if (mActiveTrack->mState == TrackBase::PAUSING) mActiveTrack->mState = TrackBase::RESUMING; - - return NO_ERROR; - } + sp <ThreadBase> strongMe = this; + status_t status = NO_ERROR; + { + AutoMutex lock(&mLock); + if (mActiveTrack != 0) { + if (recordTrack != mActiveTrack.get()) { + status = -EBUSY; + } else if (mActiveTrack->mState == TrackBase::PAUSING) { + mActiveTrack->mState = TrackBase::RESUMING; + } + return status; + } - mActiveTrack = recordTrack; - mActiveTrack->mState = TrackBase::RESUMING; - // signal thread to start - LOGV("Signal record thread"); - mWaitWorkCV.signal(); - mStartStopCond.wait(mLock); - if (mActiveTrack != 0) { + recordTrack->mState = TrackBase::IDLE; + mActiveTrack = recordTrack; + mLock.unlock(); + status_t status = AudioSystem::startInput(mId); + mLock.lock(); + if (status != NO_ERROR) { + mActiveTrack.clear(); + return status; + } + mActiveTrack->mState = TrackBase::RESUMING; + // signal thread to start + LOGV("Signal record thread"); + mWaitWorkCV.signal(); + // do not wait for mStartStopCond if exiting + if (mExiting) { + mActiveTrack.clear(); + status = INVALID_OPERATION; + goto startError; + } + mStartStopCond.wait(mLock); + if (mActiveTrack == 0) { + LOGV("Record failed to start"); + status = BAD_VALUE; + goto startError; + } LOGV("Record started OK"); - return NO_ERROR; - } else { - LOGV("Record failed to start"); - return BAD_VALUE; + return status; } +startError: + AudioSystem::stopInput(mId); + return status; } void AudioFlinger::RecordThread::stop(RecordThread::RecordTrack* recordTrack) { LOGV("RecordThread::stop"); - AutoMutex lock(&mLock); - if (mActiveTrack != 0 && recordTrack == mActiveTrack.get()) { - mActiveTrack->mState = TrackBase::PAUSING; - mStartStopCond.wait(mLock); + sp <ThreadBase> strongMe = this; + { + AutoMutex lock(&mLock); + if (mActiveTrack != 0 && recordTrack == mActiveTrack.get()) { + mActiveTrack->mState = TrackBase::PAUSING; + // do not wait for mStartStopCond if exiting + if (mExiting) { + return; + } + mStartStopCond.wait(mLock); + // if we have been restarted, recordTrack == mActiveTrack.get() here + if (mActiveTrack == 0 || recordTrack != mActiveTrack.get()) { + mLock.unlock(); + AudioSystem::stopInput(mId); + mLock.lock(); + } + } } } @@ -3388,7 +3445,7 @@ void AudioFlinger::RecordThread::audioConfigChanged(int event, int param) { break; } Mutex::Autolock _l(mAudioFlinger->mLock); - mAudioFlinger->audioConfigChanged_l(event, this, param2); + mAudioFlinger->audioConfigChanged_l(event, mId, param2); } void AudioFlinger::RecordThread::readInputParameters() @@ -3476,13 +3533,13 @@ int AudioFlinger::openOutput(uint32_t *pDevices, if ((flags & AudioSystem::OUTPUT_FLAG_DIRECT) || (format != AudioSystem::PCM_16_BIT) || (channels != AudioSystem::CHANNEL_OUT_STEREO)) { - thread = new DirectOutputThread(this, output); - LOGV("openOutput() created direct output: ID %d thread %p", (mNextThreadId + 1), thread); + thread = new DirectOutputThread(this, output, ++mNextThreadId); + LOGV("openOutput() created direct output: ID %d thread %p", mNextThreadId, thread); } else { - thread = new MixerThread(this, output); - LOGV("openOutput() created mixer output: ID %d thread %p", (mNextThreadId + 1), thread); + thread = new MixerThread(this, output, ++mNextThreadId); + LOGV("openOutput() created mixer output: ID %d thread %p", mNextThreadId, thread); } - mPlaybackThreads.add(++mNextThreadId, thread); + mPlaybackThreads.add(mNextThreadId, thread); if (pSamplingRate) *pSamplingRate = samplingRate; if (pFormat) *pFormat = format; @@ -3505,9 +3562,9 @@ int AudioFlinger::openDuplicateOutput(int output1, int output2) } - DuplicatingThread *thread = new DuplicatingThread(this, thread1); + DuplicatingThread *thread = new DuplicatingThread(this, thread1, ++mNextThreadId); thread->addOutputTrack(thread2); - mPlaybackThreads.add(++mNextThreadId, thread); + mPlaybackThreads.add(mNextThreadId, thread); return mNextThreadId; } @@ -3534,7 +3591,7 @@ status_t AudioFlinger::closeOutput(int output) } } void *param2 = 0; - audioConfigChanged_l(AudioSystem::OUTPUT_CLOSED, thread, param2); + audioConfigChanged_l(AudioSystem::OUTPUT_CLOSED, output, param2); mPlaybackThreads.removeItem(output); } thread->exit(); @@ -3628,8 +3685,8 @@ int AudioFlinger::openInput(uint32_t *pDevices, if (input != 0) { // Start record thread - thread = new RecordThread(this, input, reqSamplingRate, reqChannels); - mRecordThreads.add(++mNextThreadId, thread); + thread = new RecordThread(this, input, reqSamplingRate, reqChannels, ++mNextThreadId); + mRecordThreads.add(mNextThreadId, thread); LOGV("openInput() created record thread: ID %d thread %p", mNextThreadId, thread); if (pSamplingRate) *pSamplingRate = reqSamplingRate; if (pFormat) *pFormat = format; @@ -3655,7 +3712,7 @@ status_t AudioFlinger::closeInput(int input) LOGV("closeInput() %d", input); void *param2 = 0; - audioConfigChanged_l(AudioSystem::INPUT_CLOSED, thread, param2); + audioConfigChanged_l(AudioSystem::INPUT_CLOSED, input, param2); mRecordThreads.removeItem(input); } thread->exit(); diff --git a/libs/audioflinger/AudioFlinger.h b/libs/audioflinger/AudioFlinger.h index 594d878529..5a172949c6 100644 --- a/libs/audioflinger/AudioFlinger.h +++ b/libs/audioflinger/AudioFlinger.h @@ -213,7 +213,7 @@ private: class ThreadBase : public Thread { public: - ThreadBase (const sp<AudioFlinger>& audioFlinger); + ThreadBase (const sp<AudioFlinger>& audioFlinger, int id); virtual ~ThreadBase(); status_t dumpBase(int fd, const Vector<String16>& args); @@ -323,6 +323,7 @@ private: void sendConfigEvent(int event, int param = 0); void sendConfigEvent_l(int event, int param = 0); void processConfigEvents(); + int id() const { return mId;} mutable Mutex mLock; @@ -349,6 +350,8 @@ private: status_t mParamStatus; Vector<ConfigEvent *> mConfigEvents; bool mStandby; + int mId; + bool mExiting; }; // --- PlaybackThread --- @@ -421,6 +424,10 @@ private: void setPaused() { mState = PAUSED; } void reset(); + bool isOutputTrack() const { + return (mStreamType == AudioSystem::NUM_STREAM_TYPES); + } + // we don't really need a lock for these float mVolume[2]; volatile bool mMute; @@ -473,7 +480,7 @@ private: }; // end of OutputTrack - PlaybackThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output); + PlaybackThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id); virtual ~PlaybackThread(); virtual status_t dump(int fd, const Vector<String16>& args); @@ -573,7 +580,7 @@ private: class MixerThread : public PlaybackThread { public: - MixerThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output); + MixerThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id); virtual ~MixerThread(); // Thread virtuals @@ -600,7 +607,7 @@ private: class DirectOutputThread : public PlaybackThread { public: - DirectOutputThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output); + DirectOutputThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id); ~DirectOutputThread(); // Thread virtuals @@ -621,7 +628,7 @@ private: class DuplicatingThread : public MixerThread { public: - DuplicatingThread (const sp<AudioFlinger>& audioFlinger, MixerThread* mainThread); + DuplicatingThread (const sp<AudioFlinger>& audioFlinger, MixerThread* mainThread, int id); ~DuplicatingThread(); // Thread virtuals @@ -637,7 +644,7 @@ private: MixerThread *checkMixerThread_l(int output) const; RecordThread *checkRecordThread_l(int input) const; float streamVolumeInternal(int stream) const { return mStreamTypes[stream].volume; } - void audioConfigChanged_l(int event, const sp<ThreadBase>& thread, void *param2); + void audioConfigChanged_l(int event, int ioHandle, void *param2); friend class AudioBuffer; @@ -705,7 +712,8 @@ private: RecordThread(const sp<AudioFlinger>& audioFlinger, AudioStreamIn *input, uint32_t sampleRate, - uint32_t channels); + uint32_t channels, + int id); ~RecordThread(); virtual bool threadLoop(); diff --git a/libs/surfaceflinger/LayerBlur.cpp b/libs/surfaceflinger/LayerBlur.cpp index 5d4a38b8ab..5fd7904be6 100644 --- a/libs/surfaceflinger/LayerBlur.cpp +++ b/libs/surfaceflinger/LayerBlur.cpp @@ -169,7 +169,6 @@ void LayerBlur::onDraw(const Region& clip) const // This reads the frame-buffer, so a h/w GL would have to // finish() its rendering first. we don't want to do that // too often. Read data is 4-bytes aligned. - glFinish(); glReadPixels(X, Y, w, h, mReadFormat, mReadType, pixels); // blur that texture. diff --git a/libs/surfaceflinger/LayerBuffer.cpp b/libs/surfaceflinger/LayerBuffer.cpp index 28d7c48b45..88ef7e4300 100644 --- a/libs/surfaceflinger/LayerBuffer.cpp +++ b/libs/surfaceflinger/LayerBuffer.cpp @@ -118,7 +118,12 @@ uint32_t LayerBuffer::doTransaction(uint32_t flags) sp<Source> source(getSource()); if (source != 0) source->onTransaction(flags); - return LayerBase::doTransaction(flags); + uint32_t res = LayerBase::doTransaction(flags); + // we always want filtering for these surfaces + if (!(mFlags & DisplayHardware::SLOW_CONFIG)) { + mUseLinearFiltering = true; + } + return res; } void LayerBuffer::unlockPageFlip(const Transform& planeTransform, diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp index 872b2bc70f..450af8df09 100644 --- a/libs/utils/ResourceTypes.cpp +++ b/libs/utils/ResourceTypes.cpp @@ -42,6 +42,7 @@ #define TABLE_GETENTRY(x) //x #define TABLE_SUPER_NOISY(x) //x #define LOAD_TABLE_NOISY(x) //x +#define TABLE_THEME(x) //x namespace android { @@ -1447,18 +1448,23 @@ ssize_t ResTable::Theme::getAttribute(uint32_t resID, Res_value* outValue, const uint32_t t = Res_GETTYPE(resID); const uint32_t e = Res_GETENTRY(resID); - TABLE_NOISY(LOGV("Looking up attr 0x%08x in theme %p", resID, this)); + TABLE_THEME(LOGI("Looking up attr 0x%08x in theme %p", resID, this)); if (p >= 0) { const package_info* const pi = mPackages[p]; + TABLE_THEME(LOGI("Found package: %p", pi)); if (pi != NULL) { + TABLE_THEME(LOGI("Desired type index is %ld in avail %d", t, pi->numTypes)); if (t < pi->numTypes) { const type_info& ti = pi->types[t]; + TABLE_THEME(LOGI("Desired entry index is %ld in avail %d", e, ti.numEntries)); if (e < ti.numEntries) { const theme_entry& te = ti.entries[e]; - if (outTypeSpecFlags != NULL) { - *outTypeSpecFlags |= te.typeSpecFlags; - } + if (outTypeSpecFlags != NULL) { + *outTypeSpecFlags |= te.typeSpecFlags; + } + TABLE_THEME(LOGI("Theme value: type=0x%x, data=0x%08x", + te.value.dataType, te.value.data)); const uint8_t type = te.value.dataType; if (type == Res_value::TYPE_ATTRIBUTE) { if (cnt > 0) { @@ -1492,6 +1498,8 @@ ssize_t ResTable::Theme::resolveAttributeReference(Res_value* inOutValue, if (inOutValue->dataType == Res_value::TYPE_ATTRIBUTE) { uint32_t newTypeSpecFlags; blockIndex = getAttribute(inOutValue->data, inOutValue, &newTypeSpecFlags); + TABLE_THEME(LOGI("Resolving attr reference: blockIndex=%d, type=0x%x, data=%p\n", + (int)blockIndex, (int)inOutValue->dataType, (void*)inOutValue->data)); if (inoutTypeSpecFlags != NULL) *inoutTypeSpecFlags |= newTypeSpecFlags; //printf("Retrieved attribute new type=0x%x\n", inOutValue->dataType); if (blockIndex < 0) { @@ -1911,8 +1919,8 @@ ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex, uint32_t newFlags = 0; const ssize_t newIndex = getResource(value->data, value, true, &newFlags, outConfig); - //LOGI("Resolving reference d=%p: newIndex=%d, t=0x%02x, d=%p\n", - // (void*)lastRef, (int)newIndex, (int)value->dataType, (void*)value->data); + TABLE_THEME(LOGI("Resolving reference %p: newIndex=%d, type=0x%x, data=%p\n", + (void*)lastRef, (int)newIndex, (int)value->dataType, (void*)value->data)); //printf("Getting reference 0x%08x: newIndex=%d\n", value->data, newIndex); if (inoutTypeSpecFlags != NULL) *inoutTypeSpecFlags |= newFlags; if (newIndex < 0) { diff --git a/opengl/libagl/array.cpp b/opengl/libagl/array.cpp index 4878722f0b..71825c502a 100644 --- a/opengl/libagl/array.cpp +++ b/opengl/libagl/array.cpp @@ -1548,24 +1548,36 @@ void glDeleteBuffers(GLsizei n, const GLuint* buffers) GLuint name = buffers[i]; if (name) { // unbind bound deleted buffers... - if (c->arrays.element_array_buffer->name == name) { - c->arrays.element_array_buffer = 0; + if (c->arrays.element_array_buffer) { + if (c->arrays.element_array_buffer->name == name) { + c->arrays.element_array_buffer = 0; + } } - if (c->arrays.array_buffer->name == name) { - c->arrays.array_buffer = 0; + if (c->arrays.array_buffer) { + if (c->arrays.array_buffer->name == name) { + c->arrays.array_buffer = 0; + } } - if (c->arrays.vertex.bo->name == name) { - c->arrays.vertex.bo = 0; + if (c->arrays.vertex.bo) { + if (c->arrays.vertex.bo->name == name) { + c->arrays.vertex.bo = 0; + } } - if (c->arrays.normal.bo->name == name) { - c->arrays.normal.bo = 0; + if (c->arrays.normal.bo) { + if (c->arrays.normal.bo->name == name) { + c->arrays.normal.bo = 0; + } } - if (c->arrays.color.bo->name == name) { - c->arrays.color.bo = 0; + if (c->arrays.color.bo) { + if (c->arrays.color.bo->name == name) { + c->arrays.color.bo = 0; + } } for (int t=0 ; t<GGL_TEXTURE_UNIT_COUNT ; t++) { - if (c->arrays.texture[t].bo->name == name) { - c->arrays.texture[t].bo = 0; + if (c->arrays.texture[t].bo) { + if (c->arrays.texture[t].bo->name == name) { + c->arrays.texture[t].bo = 0; + } } } } |