Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(228)

Side by Side Diff: Source/core/animation/css/CSSAnimations.cpp

Issue 851693007: Prepare for responsive CSS animations. (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Compositor restarts after style change Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 /* 1 /*
2 * Copyright (C) 2013 Google Inc. All rights reserved. 2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 * 3 *
4 * Redistribution and use in source and binary forms, with or without 4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are 5 * modification, are permitted provided that the following conditions are
6 * met: 6 * met:
7 * 7 *
8 * * Redistributions of source code must retain the above copyright 8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer. 9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above 10 * * Redistributions in binary form must reproduce the above
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after
79 case CSSPropertyWebkitTransformOriginZ: 79 case CSSPropertyWebkitTransformOriginZ:
80 case CSSPropertyWebkitTransformOrigin: 80 case CSSPropertyWebkitTransformOrigin:
81 return CSSPropertyTransformOrigin; 81 return CSSPropertyTransformOrigin;
82 default: 82 default:
83 break; 83 break;
84 } 84 }
85 return property; 85 return property;
86 } 86 }
87 87
88 static void resolveKeyframes(StyleResolver* resolver, const Element* animatingEl ement, Element& element, const RenderStyle& style, RenderStyle* parentStyle, con st AtomicString& name, TimingFunction* defaultTimingFunction, 88 static void resolveKeyframes(StyleResolver* resolver, const Element* animatingEl ement, Element& element, const RenderStyle& style, RenderStyle* parentStyle, con st AtomicString& name, TimingFunction* defaultTimingFunction,
89 AnimatableValueKeyframeVector& keyframes) 89 StringKeyframeVector& keyframes)
90 { 90 {
91 // When the animating element is null, use its parent for scoping purposes. 91 // When the animating element is null, use its parent for scoping purposes.
92 const Element* elementForScoping = animatingElement ? animatingElement : &el ement; 92 const Element* elementForScoping = animatingElement ? animatingElement : &el ement;
93 const StyleRuleKeyframes* keyframesRule = resolver->findKeyframesRule(elemen tForScoping, name); 93 const StyleRuleKeyframes* keyframesRule = resolver->findKeyframesRule(elemen tForScoping, name);
94 if (!keyframesRule) 94 if (!keyframesRule)
95 return; 95 return;
96 96
97 const WillBeHeapVector<RefPtrWillBeMember<StyleRuleKeyframe>>& styleKeyframe s = keyframesRule->keyframes(); 97 const WillBeHeapVector<RefPtrWillBeMember<StyleRuleKeyframe>>& styleKeyframe s = keyframesRule->keyframes();
98 98
99 // Construct and populate the style for each keyframe 99 // Construct and populate the style for each keyframe
100 PropertySet specifiedPropertiesForUseCounter; 100 PropertySet specifiedPropertiesForUseCounter;
101 for (size_t i = 0; i < styleKeyframes.size(); ++i) { 101 for (size_t i = 0; i < styleKeyframes.size(); ++i) {
102 const StyleRuleKeyframe* styleKeyframe = styleKeyframes[i].get(); 102 const StyleRuleKeyframe* styleKeyframe = styleKeyframes[i].get();
103 RefPtr<RenderStyle> keyframeStyle = resolver->styleForKeyframe(element, style, parentStyle, styleKeyframe, name); 103 RefPtr<RenderStyle> keyframeStyle = resolver->styleForKeyframe(element, style, parentStyle, styleKeyframe, name);
104 RefPtrWillBeRawPtr<AnimatableValueKeyframe> keyframe = AnimatableValueKe yframe::create(); 104 RefPtrWillBeRawPtr<StringKeyframe> keyframe = StringKeyframe::create();
105 const Vector<double>& offsets = styleKeyframe->keys(); 105 const Vector<double>& offsets = styleKeyframe->keys();
106 ASSERT(!offsets.isEmpty()); 106 ASSERT(!offsets.isEmpty());
107 keyframe->setOffset(offsets[0]); 107 keyframe->setOffset(offsets[0]);
108 keyframe->setEasing(defaultTimingFunction); 108 keyframe->setEasing(defaultTimingFunction);
109 const StylePropertySet& properties = styleKeyframe->properties(); 109 const StylePropertySet& properties = styleKeyframe->properties();
110 for (unsigned j = 0; j < properties.propertyCount(); j++) { 110 for (unsigned j = 0; j < properties.propertyCount(); j++) {
111 specifiedPropertiesForUseCounter.add(properties.propertyAt(j).id()); 111 specifiedPropertiesForUseCounter.add(properties.propertyAt(j).id());
112 CSSPropertyID property = propertyForAnimation(properties.propertyAt( j).id()); 112 CSSPropertyID property = propertyForAnimation(properties.propertyAt( j).id());
113 if (property == CSSPropertyWebkitAnimationTimingFunction || property == CSSPropertyAnimationTimingFunction) { 113 if (property == CSSPropertyWebkitAnimationTimingFunction || property == CSSPropertyAnimationTimingFunction) {
114 CSSValue* value = properties.propertyAt(j).value(); 114 CSSValue* value = properties.propertyAt(j).value();
115 RefPtr<TimingFunction> timingFunction; 115 RefPtr<TimingFunction> timingFunction;
116 if (value->isInheritedValue() && parentStyle->animations()) { 116 if (value->isInheritedValue() && parentStyle->animations()) {
117 timingFunction = parentStyle->animations()->timingFunctionLi st()[0]; 117 timingFunction = parentStyle->animations()->timingFunctionLi st()[0];
118 } else if (value->isValueList()) { 118 } else if (value->isValueList()) {
119 timingFunction = CSSToStyleMap::mapAnimationTimingFunction(t oCSSValueList(value)->item(0)); 119 timingFunction = CSSToStyleMap::mapAnimationTimingFunction(t oCSSValueList(value)->item(0));
120 } else { 120 } else {
121 ASSERT(value->isInheritedValue() || value->isInitialValue() || value->isUnsetValue()); 121 ASSERT(value->isInheritedValue() || value->isInitialValue() || value->isUnsetValue());
122 timingFunction = CSSTimingData::initialTimingFunction(); 122 timingFunction = CSSTimingData::initialTimingFunction();
123 } 123 }
124 keyframe->setEasing(timingFunction.release()); 124 keyframe->setEasing(timingFunction.release());
125 } else if (CSSPropertyMetadata::isAnimatableProperty(property)) { 125 } else if (CSSPropertyMetadata::isAnimatableProperty(property)) {
126 keyframe->setPropertyValue(property, CSSAnimatableValueFactory:: create(property, *keyframeStyle).get()); 126 keyframe->setPropertyValue(property, properties.propertyAt(j).va lue());
127 } 127 }
128 } 128 }
129 keyframes.append(keyframe); 129 keyframes.append(keyframe);
130 // The last keyframe specified at a given offset is used. 130 // The last keyframe specified at a given offset is used.
131 for (size_t j = 1; j < offsets.size(); ++j) { 131 for (size_t j = 1; j < offsets.size(); ++j) {
132 keyframes.append(toAnimatableValueKeyframe(keyframe->cloneWithOffset (offsets[j]).get())); 132 keyframes.append(toStringKeyframe(keyframe->cloneWithOffset(offsets[ j]).get()));
133 } 133 }
134 } 134 }
135 135
136 for (CSSPropertyID property : specifiedPropertiesForUseCounter) { 136 for (CSSPropertyID property : specifiedPropertiesForUseCounter) {
137 ASSERT(property != CSSPropertyInvalid); 137 ASSERT(property != CSSPropertyInvalid);
138 blink::Platform::current()->histogramSparse("WebCore.Animation.CSSProper ties", UseCounter::mapCSSPropertyIdToCSSSampleIdForHistogram(property)); 138 blink::Platform::current()->histogramSparse("WebCore.Animation.CSSProper ties", UseCounter::mapCSSPropertyIdToCSSSampleIdForHistogram(property));
139 } 139 }
140 140
141 // Merge duplicate keyframes. 141 // Merge duplicate keyframes.
142 std::stable_sort(keyframes.begin(), keyframes.end(), Keyframe::compareOffset s); 142 std::stable_sort(keyframes.begin(), keyframes.end(), Keyframe::compareOffset s);
143 size_t targetIndex = 0; 143 size_t targetIndex = 0;
144 for (size_t i = 1; i < keyframes.size(); i++) { 144 for (size_t i = 1; i < keyframes.size(); i++) {
145 if (keyframes[i]->offset() == keyframes[targetIndex]->offset()) { 145 if (keyframes[i]->offset() == keyframes[targetIndex]->offset()) {
146 for (CSSPropertyID property : keyframes[i]->properties()) 146 for (CSSPropertyID property : keyframes[i]->properties())
147 keyframes[targetIndex]->setPropertyValue(property, keyframes[i]- >propertyValue(property)); 147 keyframes[targetIndex]->setPropertyValue(property, keyframes[i]- >propertyValue(property));
148 } else { 148 } else {
149 targetIndex++; 149 targetIndex++;
150 keyframes[targetIndex] = keyframes[i]; 150 keyframes[targetIndex] = keyframes[i];
151 } 151 }
152 } 152 }
153 if (!keyframes.isEmpty()) 153 if (!keyframes.isEmpty())
154 keyframes.shrink(targetIndex + 1); 154 keyframes.shrink(targetIndex + 1);
155 155
156 // Add 0% and 100% keyframes if absent. 156 // Add 0% and 100% keyframes if absent.
157 RefPtrWillBeRawPtr<AnimatableValueKeyframe> startKeyframe = keyframes.isEmpt y() ? nullptr : keyframes[0]; 157 RefPtrWillBeRawPtr<StringKeyframe> startKeyframe = keyframes.isEmpty() ? nul lptr : keyframes[0];
158 if (!startKeyframe || keyframes[0]->offset() != 0) { 158 if (!startKeyframe || keyframes[0]->offset() != 0) {
159 startKeyframe = AnimatableValueKeyframe::create(); 159 startKeyframe = StringKeyframe::create();
160 startKeyframe->setOffset(0); 160 startKeyframe->setOffset(0);
161 startKeyframe->setEasing(defaultTimingFunction); 161 startKeyframe->setEasing(defaultTimingFunction);
162 keyframes.prepend(startKeyframe); 162 keyframes.prepend(startKeyframe);
163 } 163 }
164 RefPtrWillBeRawPtr<AnimatableValueKeyframe> endKeyframe = keyframes[keyframe s.size() - 1]; 164 RefPtrWillBeRawPtr<StringKeyframe> endKeyframe = keyframes[keyframes.size() - 1];
165 if (endKeyframe->offset() != 1) { 165 if (endKeyframe->offset() != 1) {
166 endKeyframe = AnimatableValueKeyframe::create(); 166 endKeyframe = StringKeyframe::create();
167 endKeyframe->setOffset(1); 167 endKeyframe->setOffset(1);
168 endKeyframe->setEasing(defaultTimingFunction); 168 endKeyframe->setEasing(defaultTimingFunction);
169 keyframes.append(endKeyframe); 169 keyframes.append(endKeyframe);
170 } 170 }
171 ASSERT(keyframes.size() >= 2); 171 ASSERT(keyframes.size() >= 2);
172 ASSERT(!keyframes.first()->offset()); 172 ASSERT(!keyframes.first()->offset());
173 ASSERT(keyframes.last()->offset() == 1); 173 ASSERT(keyframes.last()->offset() == 1);
174 174
175 // Snapshot current property values for 0% and 100% if missing. 175 // FIXME: This is only used for use counting neutral keyframes running on th e compositor.
176 PropertySet allProperties; 176 PropertySet allProperties;
177 for (const auto& keyframe : keyframes) { 177 for (const auto& keyframe : keyframes) {
178 for (CSSPropertyID property : keyframe->properties()) 178 for (CSSPropertyID property : keyframe->properties())
179 allProperties.add(property); 179 allProperties.add(property);
180 } 180 }
181 const PropertySet& startKeyframeProperties = startKeyframe->properties(); 181 const PropertySet& startKeyframeProperties = startKeyframe->properties();
182 const PropertySet& endKeyframeProperties = endKeyframe->properties(); 182 const PropertySet& endKeyframeProperties = endKeyframe->properties();
183 bool missingStartValues = startKeyframeProperties.size() < allProperties.siz e(); 183 bool missingStartValues = startKeyframeProperties.size() < allProperties.siz e();
184 bool missingEndValues = endKeyframeProperties.size() < allProperties.size(); 184 bool missingEndValues = endKeyframeProperties.size() < allProperties.size();
185 if (missingStartValues || missingEndValues) { 185 if (missingStartValues || missingEndValues) {
186 for (CSSPropertyID property : allProperties) { 186 for (CSSPropertyID property : allProperties) {
187 bool startNeedsValue = missingStartValues && !startKeyframePropertie s.contains(property); 187 bool startNeedsValue = missingStartValues && !startKeyframePropertie s.contains(property);
188 bool endNeedsValue = missingEndValues && !endKeyframeProperties.cont ains(property); 188 bool endNeedsValue = missingEndValues && !endKeyframeProperties.cont ains(property);
189 if (!startNeedsValue && !endNeedsValue) 189 if (!startNeedsValue && !endNeedsValue)
190 continue; 190 continue;
191 RefPtrWillBeRawPtr<AnimatableValue> snapshotValue = CSSAnimatableVal ueFactory::create(property, style); 191 if (CompositorAnimations::isCompositableProperty(property))
192 if (startNeedsValue)
193 startKeyframe->setPropertyValue(property, snapshotValue.get());
194 if (endNeedsValue)
195 endKeyframe->setPropertyValue(property, snapshotValue.get());
196 if (property == CSSPropertyOpacity || property == CSSPropertyTransfo rm)
197 UseCounter::count(elementForScoping->document(), UseCounter::Syn theticKeyframesInCompositedCSSAnimation); 192 UseCounter::count(elementForScoping->document(), UseCounter::Syn theticKeyframesInCompositedCSSAnimation);
198 } 193 }
199 } 194 }
200 ASSERT(startKeyframe->properties().size() == allProperties.size());
201 ASSERT(endKeyframe->properties().size() == allProperties.size());
202 } 195 }
203 196
204 } // namespace 197 } // namespace
205 198
206 CSSAnimations::CSSAnimations() 199 CSSAnimations::CSSAnimations()
207 { 200 {
208 } 201 }
209 202
210 const AtomicString CSSAnimations::getAnimationNameForInspector(const AnimationPl ayer& player) 203 const AtomicString CSSAnimations::getAnimationNameForInspector(const AnimationPl ayer& player)
211 { 204 {
(...skipping 21 matching lines...) Expand all
233 #if !ENABLE(ASSERT) 226 #if !ENABLE(ASSERT)
234 // If we're in an animation style change, no animations can have started, be en cancelled or changed play state. 227 // If we're in an animation style change, no animations can have started, be en cancelled or changed play state.
235 // When ASSERT is enabled, we verify this optimization. 228 // When ASSERT is enabled, we verify this optimization.
236 if (activeAnimations && activeAnimations->isAnimationStyleChange()) 229 if (activeAnimations && activeAnimations->isAnimationStyleChange())
237 return; 230 return;
238 #endif 231 #endif
239 232
240 const CSSAnimationData* animationData = style.animations(); 233 const CSSAnimationData* animationData = style.animations();
241 const CSSAnimations* cssAnimations = activeAnimations ? &activeAnimations->c ssAnimations() : nullptr; 234 const CSSAnimations* cssAnimations = activeAnimations ? &activeAnimations->c ssAnimations() : nullptr;
242 235
236 bool isStyleChange = !activeAnimations || !activeAnimations->isAnimationStyl eChange();
dstockwell 2015/01/27 02:22:59 I think it's better to invert this to be bool isAn
shend 2015/01/28 02:44:50 Done.
237
243 HashSet<AtomicString> inactive; 238 HashSet<AtomicString> inactive;
244 if (cssAnimations) { 239 if (cssAnimations) {
245 for (const auto& entry : cssAnimations->m_animations) 240 for (const auto& entry : cssAnimations->m_animations)
246 inactive.add(entry.key); 241 inactive.add(entry.key);
247 } 242 }
248 243
249 if (style.display() != NONE) { 244 if (style.display() != NONE) {
250 for (size_t i = 0; animationData && i < animationData->nameList().size() ; ++i) { 245 for (size_t i = 0; animationData && i < animationData->nameList().size() ; ++i) {
251 AtomicString animationName(animationData->nameList()[i]); 246 AtomicString animationName(animationData->nameList()[i]);
252 if (animationName == CSSAnimationData::initialName()) 247 if (animationName == CSSAnimationData::initialName())
253 continue; 248 continue;
254 249
255 const bool isPaused = CSSTimingData::getRepeated(animationData->play StateList(), i) == AnimPlayStatePaused; 250 const bool isPaused = CSSTimingData::getRepeated(animationData->play StateList(), i) == AnimPlayStatePaused;
256 251
257 Timing timing = animationData->convertToTiming(i); 252 Timing timing = animationData->convertToTiming(i);
258 RefPtr<TimingFunction> keyframeTimingFunction = timing.timingFunctio n; 253 RefPtr<TimingFunction> keyframeTimingFunction = timing.timingFunctio n;
259 timing.timingFunction = Timing::defaults().timingFunction; 254 timing.timingFunction = Timing::defaults().timingFunction;
260 255
261 if (cssAnimations) { 256 if (cssAnimations) {
262 AnimationMap::const_iterator existing(cssAnimations->m_animation s.find(animationName)); 257 AnimationMap::const_iterator existing(cssAnimations->m_animation s.find(animationName));
263 if (existing != cssAnimations->m_animations.end()) { 258 if (existing != cssAnimations->m_animations.end()) {
264 inactive.remove(animationName); 259 inactive.remove(animationName);
265 260
266 AnimationPlayer* player = existing->value.get(); 261 AnimationPlayer* player = existing->value.get();
267 262
263 if (isStyleChange && player->source()->isAnimation()) {
264 AnimationEffect* effect = toAnimation(player->source())- >effect();
265 if (effect && effect->isKeyframeEffectModel()) {
dstockwell 2015/01/27 02:22:59 can effect ever be null?
shend 2015/01/28 02:44:50 I'm not sure. Some methods in the Animation class
266 KeyframeEffectModelBase* keyframeEffect = toKeyframe EffectModelBase(effect);
267 if (keyframeEffect->hasSyntheticKeyframes()) {
268 keyframeEffect->setDeferredInterpolationsOutdate d();
dstockwell 2015/01/27 02:22:58 We shouldn't be modifying state in the calculation
shend 2015/01/28 02:44:50 Done.
269 if (const RenderObject* renderer = animatingElem ent->renderer()) {
270 if (keyframeEffect->snapshotCompositableProp ertiesIfNeeded(animatingElement, *renderer->style(), style)) {
271 player->setOutdated();
dstockwell 2015/01/27 02:22:58 Ditto.
shend 2015/01/28 02:44:50 Done.
272 player->setCompositorPending(true);
273 }
274 }
275 }
276 }
277 }
278
268 // FIXME: Should handle changes in the timing function. 279 // FIXME: Should handle changes in the timing function.
269 if (timing != player->source()->specifiedTiming()) { 280 if (timing != player->source()->specifiedTiming()) {
270 ASSERT(!activeAnimations || !activeAnimations->isAnimati onStyleChange()); 281 ASSERT(isStyleChange);
271 282
272 AnimatableValueKeyframeVector resolvedKeyframes; 283 StringKeyframeVector resolvedKeyframes;
273 resolveKeyframes(resolver, animatingElement, element, st yle, parentStyle, animationName, keyframeTimingFunction.get(), resolvedKeyframes ); 284 resolveKeyframes(resolver, animatingElement, element, st yle, parentStyle, animationName, keyframeTimingFunction.get(), resolvedKeyframes );
274 285
275 update->updateAnimationTiming(player, InertAnimation::cr eate(AnimatableValueKeyframeEffectModel::create(resolvedKeyframes), 286 auto effect = StringKeyframeEffectModel::create(resolved Keyframes);
276 timing, isPaused, player->currentTimeInternal()), ti ming); 287 effect->forceConversionsToAnimatableValues(&element); // FIXME: remove this once LegacyStyleInterpolation is removed from StringKeyframe
288 effect->snapshotCompositableProperties(animatingElement, style);
289 update->updateAnimationTiming(player, InertAnimation::cr eate(effect, timing, isPaused, player->currentTimeInternal()), timing);
277 } 290 }
278 291
279 if (isPaused != player->paused()) { 292 if (isPaused != player->paused()) {
280 ASSERT(!activeAnimations || !activeAnimations->isAnimati onStyleChange()); 293 ASSERT(isStyleChange);
281 update->toggleAnimationPaused(animationName); 294 update->toggleAnimationPaused(animationName);
282 } 295 }
283 296
284 continue; 297 continue;
285 } 298 }
286 } 299 }
287 300
288 AnimatableValueKeyframeVector resolvedKeyframes; 301 StringKeyframeVector resolvedKeyframes;
289 resolveKeyframes(resolver, animatingElement, element, style, parentS tyle, animationName, keyframeTimingFunction.get(), resolvedKeyframes); 302 resolveKeyframes(resolver, animatingElement, element, style, parentS tyle, animationName, keyframeTimingFunction.get(), resolvedKeyframes);
290 if (!resolvedKeyframes.isEmpty()) { 303 if (!resolvedKeyframes.isEmpty()) {
291 ASSERT(!activeAnimations || !activeAnimations->isAnimationStyleC hange()); 304 ASSERT(isStyleChange);
292 update->startAnimation(animationName, InertAnimation::create(Ani matableValueKeyframeEffectModel::create(resolvedKeyframes), timing, isPaused, 0) ); 305 auto effect = StringKeyframeEffectModel::create(resolvedKeyframe s);
306 effect->forceConversionsToAnimatableValues(&element); // FIXME: remove this once LegacyStyleInterpolation is removed from StringKeyframe
307 effect->snapshotCompositableProperties(animatingElement, style);
308 update->startAnimation(animationName, InertAnimation::create(eff ect, timing, isPaused, 0));
293 } 309 }
294 } 310 }
295 } 311 }
296 312
297 ASSERT(inactive.isEmpty() || cssAnimations); 313 ASSERT(inactive.isEmpty() || cssAnimations);
298 for (const AtomicString& animationName : inactive) { 314 for (const AtomicString& animationName : inactive) {
299 ASSERT(!activeAnimations || !activeAnimations->isAnimationStyleChange()) ; 315 ASSERT(isStyleChange);
300 update->cancelAnimation(animationName, *cssAnimations->m_animations.get( animationName)); 316 update->cancelAnimation(animationName, *cssAnimations->m_animations.get( animationName));
301 } 317 }
302 } 318 }
303 319
304 void CSSAnimations::maybeApplyPendingUpdate(Element* element) 320 void CSSAnimations::maybeApplyPendingUpdate(Element* element)
305 { 321 {
306 if (!m_pendingUpdate) { 322 if (!m_pendingUpdate) {
307 m_previousActiveInterpolationsForAnimations.clear(); 323 m_previousActiveInterpolationsForAnimations.clear();
308 return; 324 return;
309 } 325 }
(...skipping 457 matching lines...) Expand 10 before | Expand all | Expand 10 after
767 visitor->trace(m_newTransitions); 783 visitor->trace(m_newTransitions);
768 visitor->trace(m_activeInterpolationsForAnimations); 784 visitor->trace(m_activeInterpolationsForAnimations);
769 visitor->trace(m_activeInterpolationsForTransitions); 785 visitor->trace(m_activeInterpolationsForTransitions);
770 visitor->trace(m_newAnimations); 786 visitor->trace(m_newAnimations);
771 visitor->trace(m_suppressedAnimationPlayers); 787 visitor->trace(m_suppressedAnimationPlayers);
772 visitor->trace(m_animationsWithTimingUpdates); 788 visitor->trace(m_animationsWithTimingUpdates);
773 #endif 789 #endif
774 } 790 }
775 791
776 } // namespace blink 792 } // namespace blink
OLDNEW
« Source/core/animation/CompositorAnimations.cpp ('K') | « Source/core/animation/StyleInterpolation.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698