summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/java/android/view/GLES20Canvas.java12
-rw-r--r--core/java/android/view/GLES20RenderLayer.java7
-rw-r--r--core/java/android/view/GLES20TextureLayer.java5
-rw-r--r--core/java/android/view/HardwareLayer.java12
-rw-r--r--core/java/android/view/HardwareRenderer.java137
-rw-r--r--core/jni/android_view_GLES20Canvas.cpp13
-rw-r--r--libs/hwui/Caches.cpp2
-rw-r--r--libs/hwui/OpenGLRenderer.cpp39
-rw-r--r--libs/hwui/OpenGLRenderer.h15
-rw-r--r--libs/hwui/Program.h3
-rw-r--r--libs/hwui/ProgramCache.cpp15
-rw-r--r--libs/hwui/Properties.h4
-rw-r--r--libs/hwui/thread/TaskManager.h2
13 files changed, 241 insertions, 25 deletions
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 21a03d8..cc7d948 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -274,6 +274,18 @@ class GLES20Canvas extends HardwareCanvas {
private static native int nGetStencilSize();
+ void setCountOverdrawEnabled(boolean enabled) {
+ nSetCountOverdrawEnabled(mRenderer, enabled);
+ }
+
+ static native void nSetCountOverdrawEnabled(int renderer, boolean enabled);
+
+ float getOverdraw() {
+ return nGetOverdraw(mRenderer);
+ }
+
+ static native float nGetOverdraw(int renderer);
+
///////////////////////////////////////////////////////////////////////////
// Functor
///////////////////////////////////////////////////////////////////////////
diff --git a/core/java/android/view/GLES20RenderLayer.java b/core/java/android/view/GLES20RenderLayer.java
index 685dc70..68ba77c 100644
--- a/core/java/android/view/GLES20RenderLayer.java
+++ b/core/java/android/view/GLES20RenderLayer.java
@@ -100,12 +100,17 @@ class GLES20RenderLayer extends GLES20Layer {
@Override
HardwareCanvas start(Canvas currentCanvas) {
+ return start(currentCanvas, null);
+ }
+
+ @Override
+ HardwareCanvas start(Canvas currentCanvas, Rect dirty) {
if (currentCanvas instanceof GLES20Canvas) {
((GLES20Canvas) currentCanvas).interrupt();
}
HardwareCanvas canvas = getCanvas();
canvas.setViewport(mWidth, mHeight);
- canvas.onPreDraw(null);
+ canvas.onPreDraw(dirty);
return canvas;
}
diff --git a/core/java/android/view/GLES20TextureLayer.java b/core/java/android/view/GLES20TextureLayer.java
index e863e49..a4cc630 100644
--- a/core/java/android/view/GLES20TextureLayer.java
+++ b/core/java/android/view/GLES20TextureLayer.java
@@ -63,6 +63,11 @@ class GLES20TextureLayer extends GLES20Layer {
}
@Override
+ HardwareCanvas start(Canvas currentCanvas, Rect dirty) {
+ return null;
+ }
+
+ @Override
void end(Canvas currentCanvas) {
}
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index 18b838b..23383d9 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -158,14 +158,22 @@ abstract class HardwareLayer {
/**
* This must be invoked before drawing onto this layer.
*
- * @param currentCanvas
+ * @param currentCanvas The canvas whose rendering needs to be interrupted
*/
abstract HardwareCanvas start(Canvas currentCanvas);
/**
+ * This must be invoked before drawing onto this layer.
+ *
+ * @param dirty The dirty area to repaint
+ * @param currentCanvas The canvas whose rendering needs to be interrupted
+ */
+ abstract HardwareCanvas start(Canvas currentCanvas, Rect dirty);
+
+ /**
* This must be invoked after drawing onto this layer.
*
- * @param currentCanvas
+ * @param currentCanvas The canvas whose rendering needs to be resumed
*/
abstract void end(Canvas currentCanvas);
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index c7b1253..e8c1653 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -17,6 +17,7 @@
package android.view;
import android.content.ComponentCallbacks2;
+import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
@@ -45,7 +46,6 @@ import javax.microedition.khronos.opengles.GL;
import java.io.File;
import java.io.PrintWriter;
-import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock;
import static javax.microedition.khronos.egl.EGL10.*;
@@ -165,15 +165,32 @@ public abstract class HardwareRenderer {
"debug.hwui.show_layers_updates";
/**
- * Turn on to show overdraw level.
+ * Controls overdraw debugging.
*
* Possible values:
- * "true", to enable overdraw debugging
* "false", to disable overdraw debugging
+ * "show", to show overdraw areas on screen
+ * "count", to display an overdraw counter
*
* @hide
*/
- public static final String DEBUG_SHOW_OVERDRAW_PROPERTY = "debug.hwui.show_overdraw";
+ public static final String DEBUG_OVERDRAW_PROPERTY = "debug.hwui.overdraw";
+
+ /**
+ * Value for {@link #DEBUG_OVERDRAW_PROPERTY}. When the property is set to this
+ * value, overdraw will be shown on screen by coloring pixels.
+ *
+ * @hide
+ */
+ public static final String OVERDRAW_PROPERTY_SHOW = "show";
+
+ /**
+ * Value for {@link #DEBUG_OVERDRAW_PROPERTY}. When the property is set to this
+ * value, an overdraw counter will be shown on screen.
+ *
+ * @hide
+ */
+ public static final String OVERDRAW_PROPERTY_COUNT = "count";
/**
* Turn on to debug non-rectangular clip operations.
@@ -765,6 +782,17 @@ public abstract class HardwareRenderer {
private static final int PROFILE_DRAW_THRESHOLD_STROKE_WIDTH = 2;
private static final int PROFILE_DRAW_DP_PER_MS = 7;
+ private static final String[] VISUALIZERS = {
+ PROFILE_PROPERTY_VISUALIZE_BARS,
+ PROFILE_PROPERTY_VISUALIZE_LINES
+ };
+
+ private static final String[] OVERDRAW = {
+ OVERDRAW_PROPERTY_SHOW,
+ OVERDRAW_PROPERTY_COUNT
+ };
+ private static final int OVERDRAW_TYPE_COUNT = 1;
+
static EGL10 sEgl;
static EGLDisplay sEglDisplay;
static EGLConfig sEglConfig;
@@ -810,7 +838,9 @@ public abstract class HardwareRenderer {
Paint mProfilePaint;
boolean mDebugDirtyRegions;
- boolean mShowOverdraw;
+ int mDebugOverdraw = -1;
+ HardwareLayer mDebugOverdrawLayer;
+ Paint mDebugOverdrawPaint;
final int mGlVersion;
final boolean mTranslucent;
@@ -829,18 +859,13 @@ public abstract class HardwareRenderer {
loadSystemProperties(null);
}
- private static final String[] VISUALIZERS = {
- PROFILE_PROPERTY_VISUALIZE_BARS,
- PROFILE_PROPERTY_VISUALIZE_LINES
- };
-
@Override
boolean loadSystemProperties(Surface surface) {
boolean value;
boolean changed = false;
String profiling = SystemProperties.get(PROFILE_PROPERTY);
- int graphType = Arrays.binarySearch(VISUALIZERS, profiling);
+ int graphType = search(VISUALIZERS, profiling);
value = graphType >= 0;
if (graphType != mProfileVisualizerType) {
@@ -897,11 +922,19 @@ public abstract class HardwareRenderer {
}
}
- value = SystemProperties.getBoolean(
- HardwareRenderer.DEBUG_SHOW_OVERDRAW_PROPERTY, false);
- if (value != mShowOverdraw) {
+ String overdraw = SystemProperties.get(HardwareRenderer.DEBUG_OVERDRAW_PROPERTY);
+ int debugOverdraw = search(OVERDRAW, overdraw);
+ if (debugOverdraw != mDebugOverdraw) {
changed = true;
- mShowOverdraw = value;
+ mDebugOverdraw = debugOverdraw;
+
+ if (mDebugOverdraw != OVERDRAW_TYPE_COUNT) {
+ if (mDebugOverdrawLayer != null) {
+ mDebugOverdrawLayer.destroy();
+ mDebugOverdrawLayer = null;
+ mDebugOverdrawPaint = null;
+ }
+ }
}
if (nLoadProperties()) {
@@ -911,6 +944,13 @@ public abstract class HardwareRenderer {
return changed;
}
+ private static int search(String[] values, String value) {
+ for (int i = 0; i < values.length; i++) {
+ if (values[i].equals(value)) return i;
+ }
+ return -1;
+ }
+
@Override
void dumpGfxInfo(PrintWriter pw) {
if (mProfileEnabled) {
@@ -1392,6 +1432,8 @@ public abstract class HardwareRenderer {
canvas.restoreToCount(saveCount);
view.mRecreateDisplayList = false;
+ debugOverdraw(attachInfo, dirty, canvas, displayList);
+
mFrameCount++;
debugDirtyRegions(dirty, canvas);
@@ -1411,6 +1453,61 @@ public abstract class HardwareRenderer {
}
}
+ abstract void countOverdraw(HardwareCanvas canvas);
+ abstract float getOverdraw(HardwareCanvas canvas);
+
+ private void debugOverdraw(View.AttachInfo attachInfo, Rect dirty,
+ HardwareCanvas canvas, DisplayList displayList) {
+
+ if (mDebugOverdraw == OVERDRAW_TYPE_COUNT) {
+ // TODO: Use an alpha layer allocated from a GraphicBuffer
+ // The alpha format will help with rendering performance and
+ // the GraphicBuffer will let us skip the read pixels step
+ if (mDebugOverdrawLayer == null) {
+ mDebugOverdrawLayer = createHardwareLayer(mWidth, mHeight, true);
+ } else if (mDebugOverdrawLayer.getWidth() != mWidth ||
+ mDebugOverdrawLayer.getHeight() != mHeight) {
+ mDebugOverdrawLayer.resize(mWidth, mHeight);
+ }
+
+ if (!mDebugOverdrawLayer.isValid()) {
+ mDebugOverdraw = -1;
+ return;
+ }
+
+ HardwareCanvas layerCanvas = mDebugOverdrawLayer.start(canvas, dirty);
+ countOverdraw(layerCanvas);
+ final int restoreCount = layerCanvas.save();
+ layerCanvas.drawDisplayList(displayList, null, DisplayList.FLAG_CLIP_CHILDREN);
+ layerCanvas.restoreToCount(restoreCount);
+ mDebugOverdrawLayer.end(canvas);
+
+ float overdraw = getOverdraw(layerCanvas);
+ DisplayMetrics metrics = attachInfo.mRootView.getResources().getDisplayMetrics();
+
+ drawOverdrawCounter(canvas, overdraw, metrics.density);
+ }
+ }
+
+ private void drawOverdrawCounter(HardwareCanvas canvas, float overdraw, float density) {
+ final String text = String.format("%.2fx", overdraw);
+ final Paint paint = setupPaint(density);
+ // HSBtoColor will clamp the values in the 0..1 range
+ paint.setColor(Color.HSBtoColor(0.28f - 0.28f * overdraw / 3.5f, 0.8f, 1.0f));
+
+ canvas.drawText(text, density * 4.0f, mHeight - paint.getFontMetrics().bottom, paint);
+ }
+
+ private Paint setupPaint(float density) {
+ if (mDebugOverdrawPaint == null) {
+ mDebugOverdrawPaint = new Paint();
+ mDebugOverdrawPaint.setAntiAlias(true);
+ mDebugOverdrawPaint.setShadowLayer(density * 3.0f, 0.0f, 0.0f, 0xff000000);
+ mDebugOverdrawPaint.setTextSize(density * 20.0f);
+ }
+ return mDebugOverdrawPaint;
+ }
+
private DisplayList buildDisplayList(View view, HardwareCanvas canvas) {
view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
== View.PFLAG_INVALIDATED;
@@ -2019,6 +2116,16 @@ public abstract class HardwareRenderer {
}
@Override
+ void countOverdraw(HardwareCanvas canvas) {
+ ((GLES20Canvas) canvas).setCountOverdrawEnabled(true);
+ }
+
+ @Override
+ float getOverdraw(HardwareCanvas canvas) {
+ return ((GLES20Canvas) canvas).getOverdraw();
+ }
+
+ @Override
public SurfaceTexture createSurfaceTexture(HardwareLayer layer) {
return ((GLES20TextureLayer) layer).getSurfaceTexture();
}
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index c8fa290..cd7a032 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -185,6 +185,16 @@ static void android_view_GLES20Canvas_setName(JNIEnv* env,
}
}
+static void android_view_GLES20Canvas_setCountOverdrawEnabled(JNIEnv* env, jobject clazz,
+ OpenGLRenderer* renderer, jboolean enabled) {
+ renderer->setCountOverdrawEnabled(enabled);
+}
+
+static jfloat android_view_GLES20Canvas_getOverdraw(JNIEnv* env, jobject clazz,
+ OpenGLRenderer* renderer) {
+ return renderer->getOverdraw();
+}
+
// ----------------------------------------------------------------------------
// Functor
// ----------------------------------------------------------------------------
@@ -944,6 +954,9 @@ static JNINativeMethod gMethods[] = {
{ "nSetName", "(ILjava/lang/String;)V",
(void*) android_view_GLES20Canvas_setName },
+ { "nSetCountOverdrawEnabled", "(IZ)V", (void*) android_view_GLES20Canvas_setCountOverdrawEnabled },
+ { "nGetOverdraw", "(I)F", (void*) android_view_GLES20Canvas_getOverdraw },
+
{ "nGetStencilSize", "()I", (void*) android_view_GLES20Canvas_getStencilSize },
{ "nCallDrawGLFunction", "(II)I", (void*) android_view_GLES20Canvas_callDrawGLFunction },
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index c60848c..f08b5ca 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -152,7 +152,7 @@ bool Caches::initProperties() {
if (property_get(PROPERTY_DEBUG_OVERDRAW, property, NULL) > 0) {
INIT_LOGD(" Overdraw debug enabled: %s", property);
- debugOverdraw = !strcmp(property, "true");
+ debugOverdraw = !strcmp(property, "show");
} else {
debugOverdraw = false;
}
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 025e9f8..1447eb7 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -21,7 +21,6 @@
#include <sys/types.h>
#include <SkCanvas.h>
-#include <SkPathMeasure.h>
#include <SkTypeface.h>
#include <utils/Log.h>
@@ -120,6 +119,7 @@ OpenGLRenderer::OpenGLRenderer():
mFirstSnapshot = new Snapshot;
mFrameStarted = false;
+ mCountOverdraw = false;
mScissorOptimizationDisabled = false;
}
@@ -222,6 +222,7 @@ status_t OpenGLRenderer::prepare(bool opaque) {
status_t OpenGLRenderer::prepareDirty(float left, float top,
float right, float bottom, bool opaque) {
+
setupFrameState(left, top, right, bottom, opaque);
// Layer renderers will start the frame immediately
@@ -253,7 +254,7 @@ void OpenGLRenderer::discardFramebuffer(float left, float top, float right, floa
}
status_t OpenGLRenderer::clear(float left, float top, float right, float bottom, bool opaque) {
- if (!opaque) {
+ if (!opaque || mCountOverdraw) {
mCaches.enableScissor();
mCaches.setScissor(left, mSnapshot->height - bottom, right - left, bottom - top);
glClear(GL_COLOR_BUFFER_BIT);
@@ -335,6 +336,10 @@ void OpenGLRenderer::finish() {
#endif
}
+ if (mCountOverdraw) {
+ countOverdraw();
+ }
+
mFrameStarted = false;
}
@@ -524,6 +529,21 @@ void OpenGLRenderer::renderOverdraw() {
}
}
+void OpenGLRenderer::countOverdraw() {
+ size_t count = mWidth * mHeight;
+ uint32_t* buffer = new uint32_t[count];
+ glReadPixels(0, 0, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, &buffer[0]);
+
+ size_t total = 0;
+ for (size_t i = 0; i < count; i++) {
+ total += buffer[i] & 0xff;
+ }
+
+ mOverdraw = total / float(count);
+
+ delete[] buffer;
+}
+
///////////////////////////////////////////////////////////////////////////////
// Layers
///////////////////////////////////////////////////////////////////////////////
@@ -1656,6 +1676,8 @@ void OpenGLRenderer::setupDraw(bool clear) {
mDescription.hasDebugHighlight = !mCaches.debugOverdraw &&
mCaches.debugStencilClip == Caches::kStencilShowHighlight &&
mCaches.stencil.isTestEnabled();
+
+ mDescription.emulateStencil = mCountOverdraw;
}
void OpenGLRenderer::setupDrawWithTexture(bool isAlpha8) {
@@ -3546,6 +3568,19 @@ void OpenGLRenderer::drawAlpha8TextureMesh(float left, float top, float right, f
void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode,
ProgramDescription& description, bool swapSrcDst) {
+ if (mCountOverdraw) {
+ if (!mCaches.blend) glEnable(GL_BLEND);
+ if (mCaches.lastSrcMode != GL_ONE || mCaches.lastDstMode != GL_ONE) {
+ glBlendFunc(GL_ONE, GL_ONE);
+ }
+
+ mCaches.blend = true;
+ mCaches.lastSrcMode = GL_ONE;
+ mCaches.lastDstMode = GL_ONE;
+
+ return;
+ }
+
blend = blend || mode != SkXfermode::kSrcOver_Mode;
if (blend) {
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 640c7db..df275d7 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -192,6 +192,14 @@ public:
*/
virtual void resume();
+ ANDROID_API void setCountOverdrawEnabled(bool enabled) {
+ mCountOverdraw = enabled;
+ }
+
+ ANDROID_API float getOverdraw() {
+ return mCountOverdraw ? mOverdraw : 0.0f;
+ }
+
ANDROID_API status_t invokeFunctors(Rect& dirty);
ANDROID_API void detachFunctor(Functor* functor);
ANDROID_API void attachFunctor(Functor* functor);
@@ -981,6 +989,7 @@ private:
void debugOverdraw(bool enable, bool clear);
void renderOverdraw();
+ void countOverdraw();
/**
* Should be invoked every time the glScissor is modified.
@@ -1072,6 +1081,12 @@ private:
// No-ops start/endTiling when set
bool mSuppressTiling;
+
+ // If true, this renderer will setup drawing to emulate
+ // an increment stencil buffer in the color buffer
+ bool mCountOverdraw;
+ float mOverdraw;
+
// Optional name of the renderer
String8 mName;
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
index a252209..dd1aaa2 100644
--- a/libs/hwui/Program.h
+++ b/libs/hwui/Program.h
@@ -85,6 +85,7 @@ namespace uirenderer {
#define PROGRAM_HAS_COLORS 42
#define PROGRAM_HAS_DEBUG_HIGHLIGHT 43
+#define PROGRAM_EMULATE_STENCIL 44
///////////////////////////////////////////////////////////////////////////////
// Types
@@ -163,6 +164,7 @@ struct ProgramDescription {
float gamma;
bool hasDebugHighlight;
+ bool emulateStencil;
/**
* Resets this description. All fields are reset back to the default
@@ -275,6 +277,7 @@ struct ProgramDescription {
if (isSimpleGradient) key |= programid(0x1) << PROGRAM_IS_SIMPLE_GRADIENT;
if (hasColors) key |= programid(0x1) << PROGRAM_HAS_COLORS;
if (hasDebugHighlight) key |= programid(0x1) << PROGRAM_HAS_DEBUG_HIGHLIGHT;
+ if (emulateStencil) key |= programid(0x1) << PROGRAM_EMULATE_STENCIL;
return key;
}
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 8eb85e5..367294c 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -342,6 +342,12 @@ const char* gFS_Main_ApplyColorOp[4] = {
};
const char* gFS_Main_DebugHighlight =
" gl_FragColor.rgb = vec3(0.0, gl_FragColor.a, 0.0);\n";
+const char* gFS_Main_EmulateStencil =
+ " gl_FragColor.rgba = vec4(1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0, 1.0);\n"
+ " return;\n"
+ " /*\n";
+const char* gFS_Footer_EmulateStencil =
+ " */\n";
const char* gFS_Footer =
"}\n\n";
@@ -603,7 +609,8 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
// Optimization for common cases
if (!description.isAA && !blendFramebuffer && !description.hasColors &&
description.colorOp == ProgramDescription::kColorNone &&
- !description.isPoint && !description.hasDebugHighlight) {
+ !description.isPoint && !description.hasDebugHighlight &&
+ !description.emulateStencil) {
bool fast = false;
const bool noShader = !description.hasGradient && !description.hasBitmap;
@@ -683,6 +690,9 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
// Begin the shader
shader.append(gFS_Main); {
+ if (description.emulateStencil) {
+ shader.append(gFS_Main_EmulateStencil);
+ }
// Stores the result in fragColor directly
if (description.hasTexture || description.hasExternalTexture) {
if (description.hasAlpha8Texture) {
@@ -757,6 +767,9 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
shader.append(gFS_Main_DebugHighlight);
}
}
+ if (description.emulateStencil) {
+ shader.append(gFS_Footer_EmulateStencil);
+ }
// End the shader
shader.append(gFS_Footer);
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 87ac845..7c68b5b 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -71,9 +71,9 @@ enum DebugLevel {
/**
* Used to enable/disable overdraw debugging. The accepted values are
- * "true" and "false". The default value is "false".
+ * "show", "count" and "false". The default value is "false".
*/
-#define PROPERTY_DEBUG_OVERDRAW "debug.hwui.show_overdraw"
+#define PROPERTY_DEBUG_OVERDRAW "debug.hwui.overdraw"
/**
* Used to enable/disable non-rectangular clipping debugging.
diff --git a/libs/hwui/thread/TaskManager.h b/libs/hwui/thread/TaskManager.h
index 477314b..f2a216f 100644
--- a/libs/hwui/thread/TaskManager.h
+++ b/libs/hwui/thread/TaskManager.h
@@ -43,7 +43,7 @@ public:
/**
* Returns true if this task manager can run tasks,
* false otherwise. This method will typically return
- * true on a single CPU core device.
+ * false on a single CPU core device.
*/
bool canRunTasks() const;