Merge "Lock display refresh rate when primary range is a single rate." into rvc-dev
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index deaa15f..a9f2d73 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -95,6 +95,7 @@
         while (sm == nullptr) {
             sm = interface_cast<AidlServiceManager>(ProcessState::self()->getContextObject(nullptr));
             if (sm == nullptr) {
+                ALOGE("Waiting 1s on context object on %s.", ProcessState::self()->getDriverName().c_str());
                 sleep(1);
             }
         }
diff --git a/libs/binder/include/binder/IInterface.h b/libs/binder/include/binder/IInterface.h
index 3a401ad..7116154 100644
--- a/libs/binder/include/binder/IInterface.h
+++ b/libs/binder/include/binder/IInterface.h
@@ -20,6 +20,8 @@
 
 #include <binder/Binder.h>
 
+#include <assert.h>
+
 namespace android {
 
 // ----------------------------------------------------------------------
@@ -155,7 +157,11 @@
     std::unique_ptr<I##INTERFACE> I##INTERFACE::default_impl;           \
     bool I##INTERFACE::setDefaultImpl(std::unique_ptr<I##INTERFACE> impl)\
     {                                                                   \
-        if (!I##INTERFACE::default_impl && impl) {                      \
+        /* Only one user of this interface can use this function     */ \
+        /* at a time. This is a heuristic to detect if two different */ \
+        /* users in the same process use this function.              */ \
+        assert(!I##INTERFACE::default_impl);                            \
+        if (impl) {                                                     \
             I##INTERFACE::default_impl = std::move(impl);               \
             return true;                                                \
         }                                                               \
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
index ca5be48..4835aef 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
@@ -38,6 +38,7 @@
 
     out.append("\n   ");
 
+    dumpVal(out, "bounds", bounds);
     dumpVal(out, "frame", frame);
     dumpVal(out, "viewport", viewport);
     dumpVal(out, "sourceClip", sourceClip);
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 0c8aafc..730f297 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -227,6 +227,8 @@
     if (destinationClip.isEmpty()) {
         destinationClip = displayBounds;
     }
+    // Make sure the destination clip is contained in the display bounds
+    destinationClip.intersect(displayBounds, &destinationClip);
 
     uint32_t transformOrientation;
 
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 17458e3..5b9dbf2 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -1353,8 +1353,15 @@
     // First traverse the tree and count how many layers has votes
     int layersWithVote = 0;
     traverseTree([&layersWithVote](Layer* layer) {
-        if (layer->mCurrentState.frameRate.rate > 0 ||
-            layer->mCurrentState.frameRate.type == FrameRateCompatibility::NoVote) {
+        const auto layerVotedWithDefaultCompatibility = layer->mCurrentState.frameRate.rate > 0 &&
+                layer->mCurrentState.frameRate.type == FrameRateCompatibility::Default;
+        const auto layerVotedWithNoVote =
+                layer->mCurrentState.frameRate.type == FrameRateCompatibility::NoVote;
+
+        // We do not count layers that are ExactOrMultiple for the same reason
+        // we are allowing touch boost for those layers. See
+        // RefreshRateConfigs::getBestRefreshRate for more details.
+        if (layerVotedWithDefaultCompatibility || layerVotedWithNoVote) {
             layersWithVote++;
         }
     });
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index a1ae35c..d5bebf6 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -52,6 +52,8 @@
     // Sets the display size. Client is responsible for synchronization.
     virtual void setDisplayArea(uint32_t displayArea) = 0;
 
+    virtual void setConfigChangePending(bool pending) = 0;
+
     // Marks the layer as active, and records the given state to its history.
     virtual void record(Layer*, nsecs_t presentTime, nsecs_t now) = 0;
 
@@ -78,6 +80,8 @@
 
     void setDisplayArea(uint32_t /*displayArea*/) override {}
 
+    void setConfigChangePending(bool /*pending*/) override {}
+
     // Marks the layer as active, and records the given state to its history.
     void record(Layer*, nsecs_t presentTime, nsecs_t now) override;
 
@@ -134,6 +138,8 @@
     // Sets the display size. Client is responsible for synchronization.
     void setDisplayArea(uint32_t displayArea) override { mDisplayArea = displayArea; }
 
+    void setConfigChangePending(bool pending) override { mConfigChangePending = pending; }
+
     // Marks the layer as active, and records the given state to its history.
     void record(Layer*, nsecs_t presentTime, nsecs_t now) override;
 
@@ -178,6 +184,9 @@
 
     // Whether to use priority sent from WindowManager to determine the relevancy of the layer.
     const bool mUseFrameRatePriority;
+
+    // Whether a config change is in progress or not
+    std::atomic<bool> mConfigChangePending = false;
 };
 
 } // namespace impl
diff --git a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
index 120a60f..afc8c4f 100644
--- a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
@@ -103,7 +103,7 @@
     LOG_FATAL_IF(it == mLayerInfos.end(), "%s: unknown layer %p", __FUNCTION__, layer);
 
     const auto& info = it->second;
-    info->setLastPresentTime(presentTime, now);
+    info->setLastPresentTime(presentTime, now, mConfigChangePending);
 
     // Activate layer if inactive.
     if (const auto end = activeLayers().end(); it >= end) {
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
index 255eac6..78f4433 100644
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
@@ -34,12 +34,15 @@
         mDefaultVote(defaultVote),
         mLayerVote({defaultVote, 0.0f}) {}
 
-void LayerInfoV2::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now) {
+void LayerInfoV2::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now,
+                                     bool pendingConfigChange) {
     lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));
 
     mLastUpdatedTime = std::max(lastPresentTime, now);
 
-    FrameTimeData frameTime = {.presetTime = lastPresentTime, .queueTime = mLastUpdatedTime};
+    FrameTimeData frameTime = {.presetTime = lastPresentTime,
+                               .queueTime = mLastUpdatedTime,
+                               .pendingConfigChange = pendingConfigChange};
 
     mFrameTimes.push_back(frameTime);
     if (mFrameTimes.size() > HISTORY_SIZE) {
@@ -47,23 +50,28 @@
     }
 }
 
-bool LayerInfoV2::isFrequent(nsecs_t now) const {
-    for (auto it = mFrameTimes.crbegin(); it != mFrameTimes.crend(); ++it) {
-        if (now - it->queueTime >= MAX_FREQUENT_LAYER_PERIOD_NS.count()) {
-            ALOGV("%s infrequent (last frame is %.2fms ago", mName.c_str(),
-                  (now - mFrameTimes.back().queueTime) / 1e6f);
-            return false;
+bool LayerInfoV2::isFrequent(nsecs_t now) {
+    mLastReportedIsFrequent = [&] {
+        for (auto it = mFrameTimes.crbegin(); it != mFrameTimes.crend(); ++it) {
+            if (now - it->queueTime >= MAX_FREQUENT_LAYER_PERIOD_NS.count()) {
+                ALOGV("%s infrequent (last frame is %.2fms ago)", mName.c_str(),
+                      (now - mFrameTimes.back().queueTime) / 1e6f);
+                return false;
+            }
+
+            const auto numFrames = std::distance(mFrameTimes.crbegin(), it + 1);
+            if (numFrames >= FREQUENT_LAYER_WINDOW_SIZE) {
+                ALOGV("%s frequent (burst of %zu frames)", mName.c_str(), numFrames);
+                return true;
+            }
         }
 
-        const auto numFrames = std::distance(mFrameTimes.crbegin(), it + 1);
-        if (numFrames >= FREQUENT_LAYER_WINDOW_SIZE) {
-            ALOGV("%s frequent (burst of %zu frames", mName.c_str(), numFrames);
-            return true;
-        }
-    }
+        ALOGV("%s %sfrequent (not enough frames %zu)", mName.c_str(),
+              mLastReportedIsFrequent ? "" : "in", mFrameTimes.size());
+        return mLastReportedIsFrequent;
+    }();
 
-    ALOGV("%s infrequent (not enough frames %zu)", mName.c_str(), mFrameTimes.size());
-    return false;
+    return mLastReportedIsFrequent;
 }
 
 bool LayerInfoV2::hasEnoughDataForHeuristic() const {
@@ -80,21 +88,20 @@
     return true;
 }
 
-std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible() {
-    static constexpr float MARGIN = 1.0f; // 1Hz
-
-    if (!hasEnoughDataForHeuristic()) {
-        ALOGV("Not enough data");
-        return std::nullopt;
-    }
-
-    // Calculate the refresh rate by finding the average delta between frames
+std::pair<nsecs_t, bool> LayerInfoV2::calculateAverageFrameTime() const {
     nsecs_t totalPresentTimeDeltas = 0;
     nsecs_t totalQueueTimeDeltas = 0;
-    auto missingPresentTime = false;
+    bool missingPresentTime = false;
+    int numFrames = 0;
     for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
+        // Ignore frames captured during a config change
+        if (it->pendingConfigChange || (it + 1)->pendingConfigChange) {
+            continue;
+        }
+
         totalQueueTimeDeltas +=
                 std::max(((it + 1)->queueTime - it->queueTime), mHighRefreshRatePeriod);
+        numFrames++;
 
         if (it->presetTime == 0 || (it + 1)->presetTime == 0) {
             missingPresentTime = true;
@@ -105,11 +112,6 @@
                 std::max(((it + 1)->presetTime - it->presetTime), mHighRefreshRatePeriod);
     }
 
-    // If there are no presentation timestamps provided we can't calculate the refresh rate
-    if (missingPresentTime && mLastReportedRefreshRate == 0) {
-        return std::nullopt;
-    }
-
     // Calculate the average frame time based on presentation timestamps. If those
     // doesn't exist, we look at the time the buffer was queued only. We can do that only if
     // we calculated a refresh rate based on presentation timestamps in the past. The reason
@@ -117,13 +119,18 @@
     // when implementing render ahead for specific refresh rates. When hwui no longer provides
     // presentation timestamps we look at the queue time to see if the current refresh rate still
     // matches the content.
-    const float averageFrameTime =
+    const auto averageFrameTime =
             static_cast<float>(missingPresentTime ? totalQueueTimeDeltas : totalPresentTimeDeltas) /
-            (mFrameTimes.size() - 1);
+            numFrames;
+    return {static_cast<nsecs_t>(averageFrameTime), missingPresentTime};
+}
 
-    // Now once we calculated the refresh rate we need to make sure that all the frames we captured
-    // are evenly distributed and we don't calculate the average across some burst of frames.
+bool LayerInfoV2::isRefreshRateStable(nsecs_t averageFrameTime, bool missingPresentTime) const {
     for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
+        // Ignore frames captured during a config change
+        if (it->pendingConfigChange || (it + 1)->pendingConfigChange) {
+            continue;
+        }
         const auto presentTimeDeltas = [&] {
             const auto delta = missingPresentTime ? (it + 1)->queueTime - it->queueTime
                                                   : (it + 1)->presetTime - it->presetTime;
@@ -131,10 +138,32 @@
         }();
 
         if (std::abs(presentTimeDeltas - averageFrameTime) > 2 * averageFrameTime) {
-            return std::nullopt;
+            return false;
         }
     }
 
+    return true;
+}
+
+std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible() {
+    static constexpr float MARGIN = 1.0f; // 1Hz
+
+    if (!hasEnoughDataForHeuristic()) {
+        ALOGV("Not enough data");
+        return std::nullopt;
+    }
+
+    const auto [averageFrameTime, missingPresentTime] = calculateAverageFrameTime();
+
+    // If there are no presentation timestamps provided we can't calculate the refresh rate
+    if (missingPresentTime && mLastReportedRefreshRate == 0) {
+        return std::nullopt;
+    }
+
+    if (!isRefreshRateStable(averageFrameTime, missingPresentTime)) {
+        return std::nullopt;
+    }
+
     const auto refreshRate = 1e9f / averageFrameTime;
     if (std::abs(refreshRate - mLastReportedRefreshRate) > MARGIN) {
         mLastReportedRefreshRate = refreshRate;
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.h b/services/surfaceflinger/Scheduler/LayerInfoV2.h
index 97c7017..82da7e3 100644
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.h
+++ b/services/surfaceflinger/Scheduler/LayerInfoV2.h
@@ -47,9 +47,7 @@
     // is within a threshold. If a layer is infrequent, its average refresh rate is disregarded in
     // favor of a low refresh rate.
     static constexpr size_t FREQUENT_LAYER_WINDOW_SIZE = 3;
-    static constexpr float MIN_FPS_FOR_FREQUENT_LAYER = 10.0f;
-    static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS =
-            std::chrono::nanoseconds(static_cast<nsecs_t>(1e9f / MIN_FPS_FOR_FREQUENT_LAYER)) + 1ms;
+    static constexpr std::chrono::nanoseconds MAX_FREQUENT_LAYER_PERIOD_NS = 150ms;
 
     friend class LayerHistoryTestV2;
 
@@ -63,7 +61,7 @@
     // Records the last requested present time. It also stores information about when
     // the layer was last updated. If the present time is farther in the future than the
     // updated time, the updated time is the present time.
-    void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now);
+    void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, bool pendingConfigChange);
 
     // Sets an explicit layer vote. This usually comes directly from the application via
     // ANativeWindow_setFrameRate API
@@ -93,11 +91,14 @@
     struct FrameTimeData {
         nsecs_t presetTime; // desiredPresentTime, if provided
         nsecs_t queueTime;  // buffer queue time
+        bool pendingConfigChange;
     };
 
-    bool isFrequent(nsecs_t now) const;
+    bool isFrequent(nsecs_t now);
     bool hasEnoughDataForHeuristic() const;
     std::optional<float> calculateRefreshRateIfPossible();
+    std::pair<nsecs_t, bool> calculateAverageFrameTime() const;
+    bool isRefreshRateStable(nsecs_t averageFrameTime, bool missingPresentTime) const;
 
     const std::string mName;
 
@@ -109,6 +110,13 @@
 
     float mLastReportedRefreshRate = 0.0f;
 
+    // Used to determine whether a layer should be considered frequent or
+    // not when we don't have enough frames. This member will not be cleared
+    // as part of clearHistory() to remember whether this layer was frequent
+    // or not before we processed touch boost (or anything else that would
+    // clear layer history).
+    bool mLastReportedIsFrequent = true;
+
     // Holds information about the layer vote
     struct {
         LayerHistory::LayerVoteType type;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 4eef81d..7c2af23 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -423,6 +423,12 @@
     }
 }
 
+void Scheduler::setConfigChangePending(bool pending) {
+    if (mLayerHistory) {
+        mLayerHistory->setConfigChangePending(pending);
+    }
+}
+
 void Scheduler::chooseRefreshRateForContent() {
     if (!mLayerHistory) return;
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 6eabfd2..066e9ca 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -119,6 +119,7 @@
     // Layers are registered on creation, and unregistered when the weak reference expires.
     void registerLayer(Layer*);
     void recordLayerHistory(Layer*, nsecs_t presentTime);
+    void setConfigChangePending(bool pending);
 
     // Detects content using layer history, and selects a matching refresh rate.
     void chooseRefreshRateForContent();
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 18f789e..2d25319 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1010,6 +1010,7 @@
 
         mPhaseConfiguration->setRefreshRateFps(refreshRate.getFps());
         mVSyncModulator->setPhaseOffsets(mPhaseConfiguration->getCurrentOffsets());
+        mScheduler->setConfigChangePending(true);
     }
 
     if (mRefreshRateOverlay) {
@@ -1090,6 +1091,7 @@
     mScheduler->resyncToHardwareVsync(true, refreshRate.getVsyncPeriod());
     mPhaseConfiguration->setRefreshRateFps(refreshRate.getFps());
     mVSyncModulator->setPhaseOffsets(mPhaseConfiguration->getCurrentOffsets());
+    mScheduler->setConfigChangePending(false);
 }
 
 void SurfaceFlinger::performSetActiveConfig() {
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
index d55648a..09ef06a 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
@@ -103,17 +103,8 @@
     EXPECT_TRUE(history().summarize(time).empty());
     EXPECT_EQ(0, activeLayerCount());
 
-    // The first few updates are considered infrequent
-    for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
-        history().record(layer.get(), 0, time);
-        ASSERT_EQ(1, history().summarize(time).size());
-        EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
-        EXPECT_EQ(1, activeLayerCount());
-        EXPECT_EQ(0, frequentLayerCount(time));
-    }
-
-    // Max returned if active layers have insufficient history.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
+    // The first few updates are considered frequent
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
         history().record(layer.get(), 0, time);
         ASSERT_EQ(1, history().summarize(time).size());
         EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
@@ -144,8 +135,8 @@
     history().record(layer.get(), 0, time);
     auto summary = history().summarize(time);
     ASSERT_EQ(1, history().summarize(time).size());
-    // Layer is still considered inactive so we expect to get Min
-    EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+    // Layer is still considered active so we expect to get Max
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
     EXPECT_EQ(1, activeLayerCount());
 
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
@@ -475,13 +466,13 @@
 
     nsecs_t time = systemTime();
 
-    // The first few updates are considered infrequent
-    for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
+    // The first few updates are considered frequent
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
         history().record(layer.get(), 0, time);
         ASSERT_EQ(1, history().summarize(time).size());
-        EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+        EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
         EXPECT_EQ(1, activeLayerCount());
-        EXPECT_EQ(0, frequentLayerCount(time));
+        EXPECT_EQ(1, frequentLayerCount(time));
     }
 
     // advance the time for the previous frame to be inactive
@@ -508,6 +499,36 @@
     EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
+
+    // advance the time for the previous frame to be inactive
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+
+    // Now event if we post a quick few frame we should stay infrequent
+    for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
+        history().record(layer.get(), time, time);
+        time += HI_FPS_PERIOD;
+
+        EXPECT_EQ(1, layerCount());
+        ASSERT_EQ(1, history().summarize(time).size());
+        EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+        EXPECT_EQ(1, activeLayerCount());
+        EXPECT_EQ(0, frequentLayerCount(time));
+    }
+
+    // clear the history
+    history().clear();
+
+    // Now event if we post a quick few frame we should stay infrequent
+    for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
+        history().record(layer.get(), time, time);
+        time += HI_FPS_PERIOD;
+
+        EXPECT_EQ(1, layerCount());
+        ASSERT_EQ(1, history().summarize(time).size());
+        EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+        EXPECT_EQ(1, activeLayerCount());
+        EXPECT_EQ(0, frequentLayerCount(time));
+    }
 }
 
 TEST_F(LayerHistoryTestV2, invisibleExplicitLayer) {
diff --git a/vulkan/libvulkan/api_gen.cpp b/vulkan/libvulkan/api_gen.cpp
index 37b5368..d9a9427 100644
--- a/vulkan/libvulkan/api_gen.cpp
+++ b/vulkan/libvulkan/api_gen.cpp
@@ -621,6 +621,7 @@
     // global functions
     if (instance == VK_NULL_HANDLE) {
         if (strcmp(pName, "vkCreateInstance") == 0) return reinterpret_cast<PFN_vkVoidFunction>(CreateInstance);
+        if (strcmp(pName, "vkGetInstanceProcAddr") == 0) return reinterpret_cast<PFN_vkVoidFunction>(GetInstanceProcAddr);
         if (strcmp(pName, "vkEnumerateInstanceVersion") == 0) return reinterpret_cast<PFN_vkVoidFunction>(EnumerateInstanceVersion);
         if (strcmp(pName, "vkEnumerateInstanceLayerProperties") == 0) return reinterpret_cast<PFN_vkVoidFunction>(EnumerateInstanceLayerProperties);
         if (strcmp(pName, "vkEnumerateInstanceExtensionProperties") == 0) return reinterpret_cast<PFN_vkVoidFunction>(EnumerateInstanceExtensionProperties);
diff --git a/vulkan/scripts/api_generator.py b/vulkan/scripts/api_generator.py
index 7c39075..be24172 100644
--- a/vulkan/scripts/api_generator.py
+++ b/vulkan/scripts/api_generator.py
@@ -152,7 +152,9 @@
     if (instance == VK_NULL_HANDLE) {\n""")
 
   for cmd in gencom.command_list:
-    if gencom.is_globally_dispatched(cmd):
+    # vkGetInstanceProcAddr(nullptr, "vkGetInstanceProcAddr") is effectively
+    # globally dispatched
+    if gencom.is_globally_dispatched(cmd) or cmd == 'vkGetInstanceProcAddr':
       f.write(gencom.indent(2) +
               'if (strcmp(pName, \"' + cmd +
               '\") == 0) return reinterpret_cast<PFN_vkVoidFunction>(' +