Changeset 105576 in webkit


Ignore:
Timestamp:
Jan 21, 2012 7:40:03 AM (12 years ago)
Author:
cmarrin@apple.com
Message:

Implement hardware accelerated Brightness and contrast filters
https://bugs.webkit.org/show_bug.cgi?id=75521
https://bugs.webkit.org/show_bug.cgi?id=76719

Reviewed by Simon Fraser.

Source/WebCore:

Implemented hardware accelerated brightness and contrast filters. This also fixes
the bug where grayscale filter was accidentally never getting hardware accelerated.
It also complies with proposed spec changes for the brightness filter to be additive
rather than multiplicative, according to https://bugs.webkit.org/show_bug.cgi?id=76719.
Had to make both fixes in the same patch because I had to change the allowed brightness
values for the hardware version, so I had to change the software version as well.

Tests: css3/filters/effect-brightness-hw.html

css3/filters/effect-contrast-hw.html

  • css/CSSParser.cpp:

(WebCore::CSSParser::parseBuiltinFilterArguments):

  • css/CSSStyleSelector.cpp:

(WebCore::CSSStyleSelector::createFilterOperations):

  • platform/graphics/ca/mac/PlatformCALayerMac.mm:

(PlatformCALayer::setFilters):
(PlatformCALayer::filtersCanBeComposited):

  • rendering/FilterEffectRenderer.cpp:

(WebCore::FilterEffectRenderer::build):

LayoutTests:

New tests for hardware accelerated brightness and contrast filters. Also
added brightness and contrast values to effect-combined-hw test. And changed
brightness values in effect-brightness.html to reflect new spec.
Also fixed parsing and computed style tests to reflect new brightness spec.

  • css3/filters/effect-brightness-expected.png:
  • css3/filters/effect-brightness-hw-expected.png: Added.
  • css3/filters/effect-brightness-hw-expected.txt: Added.
  • css3/filters/effect-brightness-hw.html: Added.
  • css3/filters/effect-brightness.html:
  • css3/filters/effect-combined-expected.png:
  • css3/filters/effect-combined-hw-expected.png:
  • css3/filters/effect-combined-hw-expected.txt:
  • css3/filters/effect-combined-hw.html:
  • css3/filters/effect-combined.html:
  • css3/filters/effect-contrast-hw-expected.png: Added.
  • css3/filters/effect-contrast-hw-expected.txt: Added.
  • css3/filters/effect-contrast-hw.html: Added.
  • css3/filters/filter-property-computed-style-expected.txt:
  • css3/filters/filter-property-parsing-expected.txt:
  • css3/filters/filter-property-parsing-invalid-expected.txt:
  • css3/filters/script-tests/filter-property-computed-style.js:
  • css3/filters/script-tests/filter-property-parsing-invalid.js:
  • css3/filters/script-tests/filter-property-parsing.js:
Location:
trunk
Files:
6 added
19 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r105575 r105576  
     12012-01-20  Chris Marrin  <cmarrin@apple.com>
     2
     3        Implement hardware accelerated Brightness and contrast filters
     4        https://bugs.webkit.org/show_bug.cgi?id=75521
     5        https://bugs.webkit.org/show_bug.cgi?id=76719
     6
     7        Reviewed by Simon Fraser.
     8
     9        New tests for hardware accelerated brightness and contrast filters. Also
     10        added brightness and contrast values to effect-combined-hw test. And changed
     11        brightness values in effect-brightness.html to reflect new spec.
     12        Also fixed parsing and computed style tests to reflect new brightness spec.
     13
     14        * css3/filters/effect-brightness-expected.png:
     15        * css3/filters/effect-brightness-hw-expected.png: Added.
     16        * css3/filters/effect-brightness-hw-expected.txt: Added.
     17        * css3/filters/effect-brightness-hw.html: Added.
     18        * css3/filters/effect-brightness.html:
     19        * css3/filters/effect-combined-expected.png:
     20        * css3/filters/effect-combined-hw-expected.png:
     21        * css3/filters/effect-combined-hw-expected.txt:
     22        * css3/filters/effect-combined-hw.html:
     23        * css3/filters/effect-combined.html:
     24        * css3/filters/effect-contrast-hw-expected.png: Added.
     25        * css3/filters/effect-contrast-hw-expected.txt: Added.
     26        * css3/filters/effect-contrast-hw.html: Added.
     27        * css3/filters/filter-property-computed-style-expected.txt:
     28        * css3/filters/filter-property-parsing-expected.txt:
     29        * css3/filters/filter-property-parsing-invalid-expected.txt:
     30        * css3/filters/script-tests/filter-property-computed-style.js:
     31        * css3/filters/script-tests/filter-property-parsing-invalid.js:
     32        * css3/filters/script-tests/filter-property-parsing.js:
     33
    1342012-01-21  Nikolas Zimmermann  <nzimmermann@rim.com>
    235
  • trunk/LayoutTests/css3/filters/effect-brightness.html

    r104698 r105576  
    55}
    66</script>
    7 <img style="-webkit-filter: brightness(0)" src="resources/reference.png">
    8 <img style="-webkit-filter: brightness(0.5)" src="resources/reference.png">
    9 <img style="-webkit-filter: brightness(1.0)" src="resources/reference.png">
    10 <img style="-webkit-filter: brightness(2)" src="resources/reference.png">
    11 <img style="-webkit-filter: brightness(5)" src="resources/reference.png">
    12 <img style="-webkit-filter: brightness(10)" src="resources/reference.png">
    13 <img style="-webkit-filter: brightness(1000)" src="resources/reference.png">
     7<img style="-webkit-filter: brightness(-1)" src="resources/reference.png">
     8<img style="-webkit-filter: brightness(-0.6)" src="resources/reference.png">
     9<img style="-webkit-filter: brightness(-0.3)" src="resources/reference.png">
     10<img style="-webkit-filter: brightness()" src="resources/reference.png">
     11<img style="-webkit-filter: brightness(0.3)" src="resources/reference.png">
     12<img style="-webkit-filter: brightness(0.6)" src="resources/reference.png">
     13<img style="-webkit-filter: brightness(1)" src="resources/reference.png">
  • trunk/LayoutTests/css3/filters/effect-combined-hw-expected.txt

    r103148 r105576  
    1212      RenderText {#text} at (732,96) size 4x18
    1313        text run at (732,96) width 4: " "
     14      RenderText {#text} at (180,210) size 4x18
     15        text run at (180,210) width 4: " "
    1416      RenderText {#text} at (0,0) size 0x0
    1517layer at (18,18) size 160x90
     
    2325layer at (18,132) size 160x90
    2426  RenderImage {IMG} at (10,124) size 160x90
     27layer at (202,132) size 160x90
     28  RenderImage {IMG} at (194,124) size 160x90
  • trunk/LayoutTests/css3/filters/effect-combined-hw.html

    r103148 r105576  
    1010<img style="-webkit-filter: blur(3px) hue-rotate(-90deg) sepia()" src="resources/reference.png">
    1111<img style="-webkit-filter: blur(3px) opacity(0.5) hue-rotate(-90deg) sepia()" src="resources/reference.png">
     12<img style="-webkit-filter: blur(3px) brightness(0.2) contrast(2)" src="resources/reference.png">
  • trunk/LayoutTests/css3/filters/effect-combined.html

    r104698 r105576  
    1515<img style="-webkit-filter: blur(3px) hue-rotate(-90deg) sepia()" src="resources/reference.png">
    1616<img style="-webkit-filter: blur(3px) opacity(0.5) hue-rotate(-90deg) sepia()" src="resources/reference.png">
    17 <img style="-webkit-filter: blur(3px) brightness(2)" src="resources/reference.png">
     17<img style="-webkit-filter: blur(3px) brightness(0.2) contrast(2)" src="resources/reference.png">
  • trunk/LayoutTests/css3/filters/filter-property-computed-style-expected.txt

    r103125 r105576  
    239239PASS subRule.cssText is 'brightness(1)'
    240240
    241 Value greater than 1 : brightness(2)
    242 PASS filterStyle.length is 1
    243 PASS subRule.operationType is WebKitCSSFilterValue.CSS_FILTER_BRIGHTNESS
    244 PASS subRule.cssText is 'brightness(2)'
    245 
    246241Float value converts to integer : brightness(1.0)
    247242PASS filterStyle.length is 1
     
    257252PASS filterStyle.length is 1
    258253PASS subRule.operationType is WebKitCSSFilterValue.CSS_FILTER_BRIGHTNESS
    259 PASS subRule.cssText is 'brightness(1)'
     254PASS subRule.cssText is 'brightness(0)'
    260255
    261256Multiple values : brightness(0.5) brightness(0.25)
     
    374369PASS subRule.cssText is 'blur(5px)'
    375370
    376 Percentage values : grayscale(50%) sepia(25%) saturate(75%) invert(20%) opacity(90%) brightness(130%) contrast(30%)
     371Percentage values : grayscale(50%) sepia(25%) saturate(75%) invert(20%) opacity(90%) brightness(60%) contrast(30%)
    377372PASS filterStyle.length is 7
    378373PASS subRule.operationType is WebKitCSSFilterValue.CSS_FILTER_GRAYSCALE
     
    387382PASS subRule.cssText is 'opacity(0.9)'
    388383PASS subRule.operationType is WebKitCSSFilterValue.CSS_FILTER_BRIGHTNESS
    389 PASS subRule.cssText is 'brightness(1.3)'
     384PASS subRule.cssText is 'brightness(0.6)'
    390385PASS subRule.operationType is WebKitCSSFilterValue.CSS_FILTER_CONTRAST
    391386PASS subRule.cssText is 'contrast(0.3)'
  • trunk/LayoutTests/css3/filters/filter-property-parsing-expected.txt

    r103125 r105576  
    579579PASS subRule.cssText is 'brightness(50%)'
    580580
    581 Percentage value > 1 : brightness(150%)
    582 PASS cssRule.type is 1
    583 PASS declaration.length is 1
    584 PASS declaration.getPropertyValue('-webkit-filter') is 'brightness(150%)'
    585 PASS jsWrapperClass(filterRule) is 'CSSValueList'
    586 PASS jsWrapperClass(filterRule.__proto__) is 'CSSValueListPrototype'
    587 PASS jsWrapperClass(filterRule.constructor) is 'CSSValueListConstructor'
    588 PASS filterRule.length is 1
    589 PASS subRule.operationType is WebKitCSSFilterValue.CSS_FILTER_BRIGHTNESS
    590 PASS subRule.cssText is 'brightness(150%)'
    591 
    592581Float value converts to integer : brightness(1.0)
    593582PASS cssRule.type is 1
     
    622611PASS subRule.operationType is WebKitCSSFilterValue.CSS_FILTER_BRIGHTNESS
    623612PASS subRule.cssText is 'brightness()'
    624 
    625 Value greater than one : brightness(2)
    626 PASS cssRule.type is 1
    627 PASS declaration.length is 1
    628 PASS declaration.getPropertyValue('-webkit-filter') is 'brightness(2)'
    629 PASS jsWrapperClass(filterRule) is 'CSSValueList'
    630 PASS jsWrapperClass(filterRule.__proto__) is 'CSSValueListPrototype'
    631 PASS jsWrapperClass(filterRule.constructor) is 'CSSValueListConstructor'
    632 PASS filterRule.length is 1
    633 PASS subRule.operationType is WebKitCSSFilterValue.CSS_FILTER_BRIGHTNESS
    634 PASS subRule.cssText is 'brightness(2)'
    635613
    636614Multiple values : brightness(0.5) brightness(0.25)
  • trunk/LayoutTests/css3/filters/filter-property-parsing-invalid-expected.txt

    r103125 r105576  
    215215PASS declaration.getPropertyValue('-webkit-filter') is null
    216216
    217 Negative parameter : brightness(-0.5)
    218 PASS cssRule.type is 1
    219 PASS declaration.length is 0
    220 PASS declaration.getPropertyValue('-webkit-filter') is null
    221 
    222 Negative percent : brightness(-10%)
     217Parameter out of bounds (negative) : brightness(-1.1)
     218PASS cssRule.type is 1
     219PASS declaration.length is 0
     220PASS declaration.getPropertyValue('-webkit-filter') is null
     221
     222Parameter out of bounds (positive) : brightness(101%)
    223223PASS cssRule.type is 1
    224224PASS declaration.length is 0
  • trunk/LayoutTests/css3/filters/script-tests/filter-property-computed-style.js

    r103125 r105576  
    232232                      ["brightness(1)"]);
    233233
    234 testComputedFilterRule("Value greater than 1",
    235                       "brightness(2)", 1,
    236                       ["WebKitCSSFilterValue.CSS_FILTER_BRIGHTNESS"],
    237                       ["brightness(2)"]);
    238 
    239234testComputedFilterRule("Float value converts to integer",
    240235                      "brightness(1.0)", 1,
     
    250245                      "brightness()", 1,
    251246                      ["WebKitCSSFilterValue.CSS_FILTER_BRIGHTNESS"],
    252                       ["brightness(1)"]);
     247                      ["brightness(0)"]);
    253248
    254249testComputedFilterRule("Multiple values",
     
    364359
    365360testComputedFilterRule("Percentage values",
    366                       "grayscale(50%) sepia(25%) saturate(75%) invert(20%) opacity(90%) brightness(130%) contrast(30%)", 7,
     361                      "grayscale(50%) sepia(25%) saturate(75%) invert(20%) opacity(90%) brightness(60%) contrast(30%)", 7,
    367362                      [
    368363                          "WebKitCSSFilterValue.CSS_FILTER_GRAYSCALE",
     
    380375                          "invert(0.2)",
    381376                          "opacity(0.9)",
    382                           "brightness(1.3)",
     377                          "brightness(0.6)",
    383378                          "contrast(0.3)"
    384379              ]);
  • trunk/LayoutTests/css3/filters/script-tests/filter-property-parsing-invalid.js

    r103125 r105576  
    6969testInvalidFilterRule("Too many parameters and commas", "brightness(0.5, 0.5)");
    7070testInvalidFilterRule("Trailing comma", "brightness(0.5,)");
    71 testInvalidFilterRule("Negative parameter", "brightness(-0.5)");
    72 testInvalidFilterRule("Negative percent", "brightness(-10%)");
     71testInvalidFilterRule("Parameter out of bounds (negative)", "brightness(-1.1)");
     72testInvalidFilterRule("Parameter out of bounds (positive)", "brightness(101%)");
    7373
    7474testInvalidFilterRule("Length instead of number", "contrast(10px)");
  • trunk/LayoutTests/css3/filters/script-tests/filter-property-parsing.js

    r103125 r105576  
    302302              ["brightness(50%)"]);
    303303
    304 testFilterRule("Percentage value > 1",
    305               "brightness(150%)", 1, "brightness(150%)",
    306               ["WebKitCSSFilterValue.CSS_FILTER_BRIGHTNESS"],
    307               ["brightness(150%)"]);
    308 
    309304testFilterRule("Float value converts to integer",
    310305              "brightness(1.0)", 1, "brightness(1)",
     
    321316              ["WebKitCSSFilterValue.CSS_FILTER_BRIGHTNESS"],
    322317              ["brightness()"]);
    323 
    324 testFilterRule("Value greater than one",
    325               "brightness(2)", 1, "brightness(2)",
    326               ["WebKitCSSFilterValue.CSS_FILTER_BRIGHTNESS"],
    327               ["brightness(2)"]);
    328318
    329319testFilterRule("Multiple values",
  • trunk/Source/WebCore/ChangeLog

    r105575 r105576  
     12012-01-20  Chris Marrin  <cmarrin@apple.com>
     2
     3        Implement hardware accelerated Brightness and contrast filters
     4        https://bugs.webkit.org/show_bug.cgi?id=75521
     5        https://bugs.webkit.org/show_bug.cgi?id=76719
     6
     7        Reviewed by Simon Fraser.
     8
     9        Implemented hardware accelerated brightness and contrast filters. This also fixes
     10        the bug where grayscale filter was accidentally never getting hardware accelerated.
     11        It also complies with proposed spec changes for the brightness filter to be additive
     12        rather than multiplicative, according to https://bugs.webkit.org/show_bug.cgi?id=76719.
     13        Had to make both fixes in the same patch because I had to change the allowed brightness
     14        values for the hardware version, so I had to change the software version as well.
     15
     16        Tests: css3/filters/effect-brightness-hw.html
     17               css3/filters/effect-contrast-hw.html
     18
     19        * css/CSSParser.cpp:
     20        (WebCore::CSSParser::parseBuiltinFilterArguments):
     21        * css/CSSStyleSelector.cpp:
     22        (WebCore::CSSStyleSelector::createFilterOperations):
     23        * platform/graphics/ca/mac/PlatformCALayerMac.mm:
     24        (PlatformCALayer::setFilters):
     25        (PlatformCALayer::filtersCanBeComposited):
     26        * rendering/FilterEffectRenderer.cpp:
     27        (WebCore::FilterEffectRenderer::build):
     28
    1292012-01-21  Nikolas Zimmermann  <nzimmermann@rim.com>
    230
  • trunk/Source/WebCore/css/CSSParser.cpp

    r105502 r105576  
    68126812    case WebKitCSSFilterValue::InvertFilterOperation:
    68136813    case WebKitCSSFilterValue::OpacityFilterOperation:
    6814     case WebKitCSSFilterValue::BrightnessFilterOperation:
    68156814    case WebKitCSSFilterValue::ContrastFilterOperation: {
    68166815        // One optional argument, 0-1 or 0%-100%, if missing use 100%.
     
    68256824            double amount = value->fValue;
    68266825           
    6827             // Saturate, Brightness and Contrast allow values over 100%.
     6826            // Saturate and Contrast allow values over 100%.
    68286827            if (filterType != WebKitCSSFilterValue::SaturateFilterOperation
    6829                 && filterType != WebKitCSSFilterValue::BrightnessFilterOperation
    68306828                && filterType != WebKitCSSFilterValue::ContrastFilterOperation) {
    68316829                double maxAllowed = value->unit == CSSPrimitiveValue::CSS_PERCENTAGE ? 100.0 : 1.0;
     
    68336831                    return 0;
    68346832            }
     6833
     6834            filterValue->append(cssValuePool()->createValue(amount, static_cast<CSSPrimitiveValue::UnitTypes>(value->unit)));
     6835        }
     6836        break;
     6837    }
     6838    case WebKitCSSFilterValue::BrightnessFilterOperation: {
     6839        // One optional argument, -1 to +1 or -100% to +100%, if missing use 0,
     6840        if (args->size() > 1)
     6841            return 0;
     6842
     6843        if (args->size()) {
     6844            CSSParserValue* value = args->current();
     6845            if (!validUnit(value, FNumber | FPercent, true))
     6846                return 0;
     6847               
     6848            double amount = value->fValue;
     6849            double minAllowed = value->unit == CSSPrimitiveValue::CSS_PERCENTAGE ? -100.0 : -1.0;
     6850            double maxAllowed = value->unit == CSSPrimitiveValue::CSS_PERCENTAGE ? 100.0 : 1.0;
     6851            if (amount < minAllowed || amount > maxAllowed)
     6852                return 0;
    68356853
    68366854            filterValue->append(cssValuePool()->createValue(amount, static_cast<CSSPrimitiveValue::UnitTypes>(value->unit)));
  • trunk/Source/WebCore/css/CSSStyleSelector.cpp

    r105502 r105576  
    53975397        case WebKitCSSFilterValue::ContrastFilterOperation:
    53985398        case WebKitCSSFilterValue::OpacityFilterOperation: {
    5399             double amount = 1;
     5399            double amount = (filterValue->operationType() == WebKitCSSFilterValue::BrightnessFilterOperation) ? 0 : 1;
    54005400            if (filterValue->length() == 1) {
    54015401                amount = firstValue->getDoubleValue();
  • trunk/Source/WebCore/platform/graphics/ca/mac/PlatformCALayerMac.mm

    r104698 r105576  
    721721   
    722722    for (unsigned i = 0; i < filters.size(); ++i) {
     723        String filterName = String::format("filter_%d", i);
     724       
    723725        const FilterOperation* filterOperation = filters.at(i);
    724726        switch(filterOperation->getOperationType()) {
     
    745747            [caFilter setValue:[NSNumber numberWithFloat:op->amount()] forKey:@"inputIntensity"];
    746748            [caFilter setValue:[CIColor colorWithRed:1 green:1 blue:1] forKey:@"inputColor"];
    747             [caFilter setName:@"grayscaleFilter"];
     749            [caFilter setName:filterName];
    748750            [array.get() addObject:caFilter];
    749751            break;
     
    763765            [caFilter setValue:[CIVector vectorWithX:0 Y:0 Z:0 W:1] forKey:@"inputAVector"];
    764766            [caFilter setValue:[CIVector vectorWithX:0 Y:0 Z:0 W:0] forKey:@"inputBiasVector"];
    765             [caFilter setName:@"sepiaFilter"];
     767            [caFilter setName:filterName];
    766768            [array.get() addObject:caFilter];
    767769            break;
     
    772774            [caFilter setDefaults];
    773775            [caFilter setValue:[NSNumber numberWithFloat:op->amount()] forKey:@"inputSaturation"];
    774             [caFilter setName:@"saturateFilter"];
     776            [caFilter setName:filterName];
    775777            [array.get() addObject:caFilter];
    776778            break;
     
    783785            // The CIHueAdjust value is in radians
    784786            [caFilter setValue:[NSNumber numberWithFloat:op->amount() * M_PI * 2 / 360] forKey:@"inputAngle"];
    785             [caFilter setName:@"hueRotateFilter"];
     787            [caFilter setName:filterName];
    786788            [array.get() addObject:caFilter];
    787789            break;
     
    799801            [caFilter setValue:[CIVector vectorWithX:0 Y:0 Z:0 W:1] forKey:@"inputAVector"];
    800802            [caFilter setValue:[CIVector vectorWithX:op->amount() Y:op->amount() Z:op->amount() W:0] forKey:@"inputBiasVector"];
    801             [caFilter setName:@"invertFilter"];
     803            [caFilter setName:filterName];
    802804            [array.get() addObject:caFilter];
    803805            break;
     
    813815            [caFilter setValue:[CIVector vectorWithX:0 Y:0 Z:0 W:op->amount()] forKey:@"inputAVector"];
    814816            [caFilter setValue:[CIVector vectorWithX:0 Y:0 Z:0 W:0] forKey:@"inputBiasVector"];
    815             [caFilter setName:@"opacityFilter"];
     817            [caFilter setName:filterName];
    816818            [array.get() addObject:caFilter];
    817819            break;
     
    823825            [caFilter setDefaults];
    824826            [caFilter setValue:[NSNumber numberWithFloat:op->stdDeviation().calcFloatValue(0)] forKey:@"inputRadius"];
    825             [caFilter setName:@"blurFilter"];
     827            [caFilter setName:filterName];
     828            [array.get() addObject:caFilter];
     829            break;
     830        }
     831        case FilterOperation::CONTRAST: {
     832            const BasicComponentTransferFilterOperation* op = static_cast<const BasicComponentTransferFilterOperation*>(filterOperation);
     833            CIFilter* caFilter = [CIFilter filterWithName:@"CIColorControls"];
     834            [caFilter setDefaults];
     835            [caFilter setValue:[NSNumber numberWithFloat:op->amount()] forKey:@"inputContrast"];
     836            [caFilter setName:filterName];
     837            [array.get() addObject:caFilter];
     838            break;
     839        }
     840        case FilterOperation::BRIGHTNESS: {
     841            const BasicComponentTransferFilterOperation* op = static_cast<const BasicComponentTransferFilterOperation*>(filterOperation);
     842            CIFilter* caFilter = [CIFilter filterWithName:@"CIColorControls"];
     843            [caFilter setDefaults];
     844            [caFilter setValue:[NSNumber numberWithFloat:op->amount()] forKey:@"inputBrightness"];
     845            [caFilter setName:filterName];
    826846            [array.get() addObject:caFilter];
    827847            break;
     
    851871        switch(filterOperation->getOperationType()) {
    852872        case FilterOperation::REFERENCE:
    853         case FilterOperation::GRAYSCALE:
    854         case FilterOperation::BRIGHTNESS:
    855         case FilterOperation::CONTRAST:
    856873#if ENABLE(CSS_SHADERS)
    857874        case FilterOperation::CUSTOM:
  • trunk/Source/WebCore/rendering/FilterEffectRenderer.cpp

    r105485 r105576  
    217217            ComponentTransferFunction transferFunction;
    218218            transferFunction.type = FECOMPONENTTRANSFER_TYPE_LINEAR;
    219             transferFunction.slope = narrowPrecisionToFloat(componentTransferOperation->amount());
     219            transferFunction.slope = 1;
     220            transferFunction.intercept = narrowPrecisionToFloat(componentTransferOperation->amount());
    220221
    221222            ComponentTransferFunction nullFunction;
Note: See TracChangeset for help on using the changeset viewer.