diff --git a/src/Drawable.js b/src/Drawable.js
index d6d66581..208e0812 100644
--- a/src/Drawable.js
+++ b/src/Drawable.js
@@ -452,6 +452,8 @@ class Drawable {
 
     /**
      * Check if the world position touches the skin.
+     * The caller is responsible for ensuring this drawable's inverse matrix & its skin's silhouette are up-to-date.
+     * @see updateCPURenderAttributes
      * @param {twgl.v3} vec World coordinate vector.
      * @return {boolean} True if the world position touches the skin.
      */
@@ -632,6 +634,15 @@ class Drawable {
         }
     }
 
+    /**
+     * Update everything necessary to render this drawable on the CPU.
+     */
+    updateCPURenderAttributes () {
+        this.updateMatrix();
+        // CPU rendering always occurs at the "native" size, so no need to scale up this._scale
+        if (this.skin) this.skin.updateSilhouette(this._scale);
+    }
+
     /**
      * Respond to an internal change in the current Skin.
      * @private
@@ -676,6 +687,8 @@ class Drawable {
 
     /**
      * Sample a color from a drawable's texture.
+     * The caller is responsible for ensuring this drawable's inverse matrix & its skin's silhouette are up-to-date.
+     * @see updateCPURenderAttributes
      * @param {twgl.v3} vec The scratch space [x,y] vector
      * @param {Drawable} drawable The drawable to sample the texture from
      * @param {Uint8ClampedArray} dst The "color4b" representation of the texture at point.
diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js
index 5c9580bc..7103f5ef 100644
--- a/src/RenderWebGL.js
+++ b/src/RenderWebGL.js
@@ -985,12 +985,7 @@ class RenderWebGL extends EventEmitter {
         const bounds = this.clientSpaceToScratchBounds(centerX, centerY, touchWidth, touchHeight);
         const worldPos = twgl.v3.create();
 
-        drawable.updateMatrix();
-        if (drawable.skin) {
-            drawable.skin.updateSilhouette(this._getDrawableScreenSpaceScale(drawable));
-        } else {
-            log.warn(`Could not find skin for drawable with id: ${drawableID}`);
-        }
+        drawable.updateCPURenderAttributes();
 
         for (worldPos[1] = bounds.bottom; worldPos[1] <= bounds.top; worldPos[1]++) {
             for (worldPos[0] = bounds.left; worldPos[0] <= bounds.right; worldPos[0]++) {
@@ -1021,12 +1016,7 @@ class RenderWebGL extends EventEmitter {
             const drawable = this._allDrawables[id];
             // default pick list ignores visible and ghosted sprites.
             if (drawable.getVisible() && drawable.getUniforms().u_ghost !== 0) {
-                drawable.updateMatrix();
-                if (drawable.skin) {
-                    drawable.skin.updateSilhouette(this._getDrawableScreenSpaceScale(drawable));
-                } else {
-                    log.warn(`Could not find skin for drawable with id: ${id}`);
-                }
+                drawable.updateCPURenderAttributes();
                 return true;
             }
             return false;
@@ -1258,8 +1248,8 @@ class RenderWebGL extends EventEmitter {
         /** @todo remove this once URL-based skin setting is removed. */
         if (!drawable.skin || !drawable.skin.getTexture([100, 100])) return null;
 
-        drawable.updateMatrix();
-        drawable.skin.updateSilhouette(this._getDrawableScreenSpaceScale(drawable));
+
+        drawable.updateCPURenderAttributes();
         const bounds = drawable.getFastBounds();
 
         // Limit queries to the stage size.
@@ -1296,8 +1286,7 @@ class RenderWebGL extends EventEmitter {
                 const drawable = this._allDrawables[id];
                 if (drawable.skin && drawable._visible) {
                     // Update the CPU position data
-                    drawable.updateMatrix();
-                    drawable.skin.updateSilhouette(this._getDrawableScreenSpaceScale(drawable));
+                    drawable.updateCPURenderAttributes();
                     const candidateBounds = drawable.getFastBounds();
                     if (bounds.intersects(candidateBounds)) {
                         result.push({
@@ -1775,8 +1764,7 @@ class RenderWebGL extends EventEmitter {
     _getConvexHullPointsForDrawable (drawableID) {
         const drawable = this._allDrawables[drawableID];
 
-        drawable.updateMatrix();
-        drawable.skin.updateSilhouette(this._getDrawableScreenSpaceScale(drawable));
+        drawable.updateCPURenderAttributes();
 
         const [width, height] = drawable.skin.size;
         // No points in the hull if invisible or size is 0.
diff --git a/src/SVGSkin.js b/src/SVGSkin.js
index 9699b459..8da356f4 100644
--- a/src/SVGSkin.js
+++ b/src/SVGSkin.js
@@ -105,7 +105,7 @@ class SVGSkin extends Skin {
         return mip;
     }
 
-    updateSilhouette (scale = 1) {
+    updateSilhouette (scale = [100, 100]) {
         // Ensure a silhouette exists.
         this.getTexture(scale);
     }
diff --git a/src/Skin.js b/src/Skin.js
index 06cedd85..3a740a8f 100644
--- a/src/Skin.js
+++ b/src/Skin.js
@@ -222,6 +222,9 @@ class Skin extends EventEmitter {
     /**
      * Does this point touch an opaque or translucent point on this skin?
      * Nearest Neighbor version
+     * The caller is responsible for ensuring this skin's silhouette is up-to-date.
+     * @see updateSilhouette
+     * @see Drawable.updateCPURenderAttributes
      * @param {twgl.v3} vec A texture coordinate.
      * @return {boolean} Did it touch?
      */
@@ -232,6 +235,9 @@ class Skin extends EventEmitter {
     /**
      * Does this point touch an opaque or translucent point on this skin?
      * Linear Interpolation version
+     * The caller is responsible for ensuring this skin's silhouette is up-to-date.
+     * @see updateSilhouette
+     * @see Drawable.updateCPURenderAttributes
      * @param {twgl.v3} vec A texture coordinate.
      * @return {boolean} Did it touch?
      */
diff --git a/test/integration/cpu-render.html b/test/integration/cpu-render.html
index 26a79c3f..7eec0a61 100644
--- a/test/integration/cpu-render.html
+++ b/test/integration/cpu-render.html
@@ -42,8 +42,7 @@
                 if (!(drawable._visible && drawable.skin)) {
                     return;
                 }
-                drawable.updateMatrix();
-                drawable.skin.updateSilhouette();
+                drawable.updateCPURenderAttributes();
                 return { id, drawable };
             }).reverse().filter(Boolean);
             const color = new Uint8ClampedArray(3);